Component 组件化框架

Posted 陈旭金-小金子

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Component 组件化框架相关的知识,希望对你有一定的参考价值。

前言

大家好, 此文用一个较详细的叙述来介绍 android 的组件化框架 Component, 我从 17 年开始设计并且研究组件化框架的. 以及和其他框架相比, 为什么更优秀, 更好用。下文且听我细细道来~

什么是组件化

其实最简短的介绍就是下面几件事:

  • 代码的隔离
  • 资源的隔离
  • 当代码和资源隔离的时候, 各个平行的业务模块如何进行交互
    • 跨模块的调用
    • 路由跳转 Activity 跳转 和 Fragment 获取

其实上面的模块交互中, 核心是 跨模块调用, 但是为什么路由跳转也同样重要呢?而且要单拎出来.

是因为在 Android 环境中, 到处充斥着 Activity跳转、获取Fragment. 这种操作特别多. 如果你利用跨模块调用去做, 你会发现虽然能做, 但是十分的不方便, 而且不人性化, 所以路由跳转也同样重要

哪些是组件化框架

其实只要解决了最根本的跨模块调用功能的都算组件化框架。下面的分析不带有攻击性~~~

比如 CC, 它的设计就是解决了跨模块调用的. 它的出发点就是基于解决 跨模块调用这点. 它将这方面做得很好.

基本上除了它之外的其他框架, 都是基于 URI 设计的. 会比较侧重于路由方面的功能, 跨模块调用为辅. 比如

Component 介绍

Component 除了实现了基本的路由跳转, 跨服务调用等基本功能外. 还有很多令人欣喜的功能

跳转获取目标界面 ActivityResult

我们很多人都可能遇到一个不是经常遇到的问题, 也就是在某一个地方( Adapter, Dialog,Service ), 忽然要跳转一个界面去获取这个界面返回的 ActivityResult, 这个很多人都会说, 可以使用 EventBus 之类的库呀. 那你赢了好吧. 确实可以用, 但是不鼓励

如果不借助任何的库, 你得先拿到 Activity 去调用 startActivityForResult, 然后重写 ActivityonActivityResult 方法去获取到 Intent 进而拿到数据, 传给需要的地方. 流程图如下:

很明显, 代码不仅写得多, 而且完全没有技术含量, 写多了真的会吐的有没有. 而且每次还得和 Activity 建立接口或者方法的交互.

Component 是怎么做的呢?

Router
                .with(getContext())
                .host("component1")
                .path("main")
                .putString("name", "cxj")
                .putString("pass", "123")
                .requestCode(456) // requestCode
                .forwardForResult(new BiCallback.BiCallbackAdapter<ActivityResult>() 
                    @Override
                    public void onSuccess(@NonNull RouterResult result, 
                                          @NonNull ActivityResult activityResult) 
                        super.onSuccess(result, activityResult);
                        // 你就拿到了 ActivityResult
                    
                );
  • 没有了任何的和 Activity 交互的环节,
  • 在回调中立马得到目标界面返回的数据.
  • Component 可以让你的代码更加的紧凑, 不会因为你需要 startActivityForResult 而分割你的代码.
  • 对目标界面 0 入侵, 目标界面完成原生写法即可.

是不是很爽?会很好的让你写代码心情愉悦. 内部实现原理其实和 GlideRxPermission 一致, 都是通过预埋一个 Fragment 实现的.

当然了这部分代码其实可以抽取出来做成一个工具类啥的, 有兴趣的可以研究下. 并不是只能依赖 Component 才能拥有此功能

页面拦截器(思路最早貌似源自WMRouter)

什么是页面拦截器

我们知道很多框架都有拦截器的概念. 页面拦截器说白了. 就是只对某些路由请求有效的拦截器. 画图如下

上图可以很清晰的说明, 当你的路由是 router://order/detail 你目标是订单详情界面, 会经过两个全局拦截器.

当框架发现你跳转的界面有一个 登陆拦截器( 页面拦截器), 则框架就会先执行登陆拦截器, 登陆拦截器会完成整个登陆的过程, 在登陆成功之后, 自动完成之前的跳转.

上图解决了一个啥问题呢?

你在平时的写代码中, 你处理这些场景只有两种方式:

  • 在每一次跳转之前先解决好跳转到目标界面的先决条件

    • 缺点:每一处跳转都需要写相同的代码去处理先决条件
  • 在目标界面进行需要的先决条件的处理.

    • 缺点:目标界面已经启动, 你可能需要做. 比如(登陆、定位)失败之后的显示. 你也可能推出该界面. 但是速度不快的话, 用户还可能看到闪一下!

两种方式都有各自的缺点, 都挺恶心的. Component 呢就很巧妙的在跳转前处理了, 但是却不用发起的界面多写任何一句代码.

有些人说 ARouter 也能实现啊, 那么区别和优势在哪里呢?

  • 全局拦截和非全局拦截的区别
    • Arouter 每一个用注解声明的拦截器都是全局拦截器, 也就是说每一个路由都会执行. 而 Component 的页面拦截器只有当最终跳转的目标是某个界面的时候, 某个界面上标记的拦截器才会被执行. 所以页面拦截器不会每一个路由都会执行到.
  • 跳转的自动完成
    • ARouter 当判断到需要登录的时候, 会帮你跳转到登录, 本次跳转就结束了. 当你完成登陆之后, 你需要再次发起跳转.
    • Component 在执行到某个拦截器, 拦截器会负责帮你完成需要的工作, 然后才会继续之前的跳转. 不用用户去再次发起跳转.

此处展示一个订单详情的标记范例:

@RouterAnno(
  			// host 可省略不写
        host = "order", 
        path = "detail",
  			// 页面拦截器(此处是一个登陆拦截器, 完成自动登陆)
        interceptorNames = "user.login",
        desc = "订单详情界面"
)
public class OrderDetailAct extends AppCompatActivity
				// ........

利用自定义 Intent 标记第三方界面或者系统界面

我们用过组件化框架的人都知道, 所有组件化框架都是针对项目中的 Activity 可以通过框架路由. 但是系统和第三方的界面就不能囊括在内. 因为基于 URI 设计的那些组件化框架都没法对系统的界面进行一个路由标记. 所以没法跳转. 但是其实这又是一个很有必要的功能.

所以 Component 支持你自定义 Intent. 如下就是自定义了一个静态方法, 返回系统的拍照界面的 Intent, 并且对拍照界面的 Intent 标记了一个 页面拦截器 去处理拍照权限的问题. 这时候你在任何的地方只需要

Router.with(this).hostAndPath("system/takePhone").forward() 即可完成跳转, 并且不需要关心权限问题.

/**
 * 拍照界面,需要拍照权限
 */
@Nullable
@RouterAnno(
    host = "system",
    path = "takePhone",
  	// 拍照权限申请拦截器
    interceptorNames = "help.cameraPermision"
)
public static Intent takePictureIntent(@NonNull RouterRequest request) 
    Intent intent = new Intent();
    // 指定开启系统相机的Action
    intent.setAction(MediaStore.ACTION_IMAGE_CAPTURE);
    intent.addCategory(Intent.CATEGORY_DEFAULT);
    return intent;

自定义 Intent 可以实现让你对任何一个界面进行标记, 系统界面、第三方界面、你自己写的界面. 从而实现任何一个界面都可路由.

而自定义 Intent页面拦截器 配合就可以达到更妙的效果. 如下为示意图.

路由 Api

我们都用过 Retrofit, 它为何受欢迎?是因为它把对一个网络接口的调用用一个接口方法来描述出来. 方便、易用. 并且让你的所有网络接口都聚集在一块. 好维护

那我们组件化为何会有路由 Api 呢?

有很多朋友反馈呀. 当跳转到一个界面的入口特别多的时候. 如下的代码就会被 copy 很多遍, 当有改动的时候改的地方多, 并且不好找.

Router.with(this)
  		.hostAndPath("order/detail")
  		.putString("xxx1","xxx")
  		.putInt("xxx2", 5)
  		.putLog("xxx3", 555L)
  		.forward();

在没有组件化之前, 代码没有分离之前, 很多小伙伴会在目标界面写一个静态的 startAct 方法, 跳转到该界面都会调用此方法. 现在组件化了很多时候都像上述一样 Copy, 不好维护.

首先这个问题在我看来, 其实算是一个问题也不算一个问题. 在我看来, 组件化之后, 代码和资源的隔离, 肯定会出现这种情况. 其实也是一个必然性. 但是为了广大的朋友, 我这边特别注意到一个开源库的路由就是使用路由 Api 做的

ARetrofit 我觉得很棒的思路. 于是后面我也支持了这个功能. 最简单的一个范例:

@RouterApiAnno()
@HostAnno("user")
public interface UserApi 
      @PathAnno("login")
      void toLoginView(
          Context context,
          @ParameterAnno("name") String name,
          @ParameterAnno("pass") String pass,
      );

// 声明一个上述的接口, 然后你就可以下面这样子使用啦
Router.withApi(UserApi.class).toLoginView(this, "xiaojinzi", "123");

这种方式可以很好地让某一些入口特别多的跳转集中调用这个方法, 达到维护方便的作用!

看个人喜好. 我个人会更喜欢直接使用代码跳转.

拦截器的执行线程

为什么会聊这个话题. 有些人觉得拦截器的执行线程有啥好聊的, 肯定在子线程啊. 但是我也得告诉你. 得分场景.

很多路由框架, 拦截器的执行线程在子线程, 是为了能让拦截器做任何事情. 因为它们的设计上, 路由跳转和服务发现(也可以叫做跨模块功能调用) 是设计在一块的. 拦截器既能拦截到路由跳转, 也能拦截到功能的调用.

我不评价这种设计的好坏. Component 的服务发现和路由跳转是完全分离的两个过程.

Component 中只有 路由跳转 有拦截器的概念. 那么针对 路由跳转 . 不针对 功能调用 的场景下, 其实 拦截器 的执行线程应该在主线程更合理. 原因如下:

  • 平常的 98% 代码都是写在主线程的. 除非你写的是关于 sdk, 框架, 网络库 … 换句话说就是纯功能的库或者组件.
  • 如果在子线程你想弹个加载框框, 你切线程?让当前线程先等着?然后各种线程的唤起?我想这个不是你想要的
  • 拦截器即使设计在主线程执行, 也可以做任何事情. 做法就是让返回值变为 void, 什么时候往下执行, 需要用户手动调用.

比如这个权限申请的拦截器, 你什么时候处理好你要做的事情, 你什么时候调用 chain.proceed(chain.request()); 让拦截器继续, 如果处理失败, 你调用 CallbackonError 返回错误即可. 这种功能, 其他框架处理的话, 就麻烦喽。。。

/**
 * 电话权限申请的拦截器
 */
@InterceptorAnno("help.callPhoePermision")
public class CallPhoePermisionInterceptor implements RouterInterceptor 
  @Override
  public void intercept(final Chain chain) throws Exception 
    PermissionsUtil.with(chain.request().getRawContext())
      .request(Manifest.permission.CALL_PHONE)
      .execute(new PermissionsCallback() 
        @Override
        public void onResult(boolean granted) 
          if (granted) 
            chain.proceed(chain.request());
           else 
            chain.callback()
              .onError(
              		new Exception("fail to request call phone permision")
            	);
          
        
      );
  

路由自动取消(相关界面销毁之后)

当你用上组件化框架去跳转, 你会发现整个路由的过程就不是立马跳转了, 而是经过一段时间的.

你可以任何和处理一个任务、一个耗时请求一样. 所以自动取消路由功能看起来没啥用, 其实挺有用:

比如用户点击了一个按钮, 发起跳转. 中间有一个拦截器呢, 发现要处理一个任务. 于是去做任务了.

这时候用户等不及了, 点击了返回键。。。。可想而知, 之前发出去的路由如果不能被取消, 当他路由失败的时候, 回调的时候. 因为你没有判断界面是否销毁, 处理不得当. 奔溃了。。。

所以 Component 中, 如果你的路由发起的时候是 Fragment 或者 Activity. 当相关的 Fragment 或者 Activity 销毁了, 路由自动取消. 不会回调的.

Component 也完全支持 RxJava

Component 可以让一个跳转返回一个 [Observable], 让你可以把跳转和其他 [Observable] 去结合使用.

比如你们用的 Retrofit 支持 RxJava 返回一个 [Observable]. 同理, Component 也完全支持

比如下面的范例

Completable completable = RxRouter
                .with(this)
                .host("order")
                .path("detail")
                .call();
completable.subscribe();

Single<ActivityResult> single = RxRouter
                .with(this)
                .host("help")
                .path("address")
                .activityResultCall();
single.subscribe(activityResult -> 
  		 System.out.println("拿到 ActivityResult");
);

其他

  • 跳转 Fragment
  • 支持 Androidx
  • 完善的 Idea Plugin
  • 无缝对接 H5
  • 依赖注入参数和服务
  • 支持跳转动画
  • 支持多 module
  • 支持业务组件的生命周期
  • 更多的介绍就不一一展开了 …

写在最后

和其他基于 URI 框架相比, Component 的功能算是最全面了, 而且稳定!

有兴趣的请去我的 github 查看源码. 喜欢我的框架的小伙帮请不要吝啬你的 star 为我加油吧!

以上是关于Component 组件化框架的主要内容,如果未能解决你的问题,请参考以下文章

维护4年的组件化框架 Component 升级到 KComponent 啦

在半透明框架/面板/组件上重新绘制。

Unityclient框架笔记二(组件实体开发模式的思考)

Component 初识组件

Spring框架开发底层的@Component 通用组件模式@Service 服务模式@Configuration 配置模式等注解装配技术详解

前端框架vue.js系列:Vue.extendVue.component与new Vue