Component 组件化框架

前言

你们好, 此文用一个较详细的叙述来介绍 Android 的组件化框架 Component, 我从 17 年开始设计而且研究组件化框架的. 以及和其余框架相比, 为何更优秀, 更好用。下文且听我细细道来~java

什么是组件化

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

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

其实上面的模块交互中, 核心是 跨模块调用, 可是为何路由跳转也一样重要呢?并且要单拎出来.github

是由于在 Android 环境中, 处处充斥着 Activity跳转、获取Fragment. 这种操做特别多. 若是你利用跨模块调用去作, 你会发现虽然能作, 可是十分的不方便, 并且不人性化, 因此路由跳转也一样重要web

哪些是组件化框架

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

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

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

Component 介绍

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

跳转获取目标界面 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 为我加油吧!