文章

“终于懂了” 系列:组件化框架 ARouter 完全解析(一) 原理详解

“终于懂了” 系列:组件化框架 ARouter 完全解析(一) 原理详解

本文转载自 “终于懂了” 系列:组件化框架 ARouter 完全解析(一) 原理详解(作者:胡飞洋)。版权归原作者所有,此处仅作个人学习备份。

前言

在我之前的组件化文章《“终于懂了” 系列:Android组件化,全面掌握!》中,提到为了实现组件化要解决的几个问题点,其中 页面跳转组件间通信 的问题是使用了 ARouter 这个框架来解决的。ARouter确实是专门用于做组件化改造,官方是这么介绍的:

一个用于帮助 Android App 进行组件化改造的框架 —— 支持模块间的路由、通信、解耦

关于组件化的知识已在上面文章中全面介绍了。那么是时候对 ARouter 这个强大的框架做一个解析了:它是如何做到 页面跳转、组件间通信 的?我们能从ARrouter中学到哪些东西?

由于内容预计较多,分为三篇,分别介绍 ARouter的实现原理、框架使用到的相关通用技术。本篇就先梳理ARouter的实现原理,看看它是如何做到跨组件跳转页面、获取服务。

一、路由认知

ARouter从命名即可知,这是一个路由框架。那么路由是个啥呢?

路由routing)就是通过互联的网络把信息源地址传输到目的地址的活动。– 百科 可见 路由 是个动词,这是网络传输中的概念,完成路由这个操作的实体设备就是 路由器(Router)。

另外,生活中的 信件邮寄 也可以理解为一个 路由过程:邮局把信件从邮寄方 传输到接收人的手上。首先 邮寄方 和 接收人 是无法接触的(无耦合依赖),只能通过 邮局这个第三方 完成邮寄;邮局根据信封上的地址,例如 “深圳市 深圳大学粤海校区”,决定分发到 开往深圳的车上,然后深圳的邮递员找到 深圳大学粤海校区 对应的 “南山区南海大道3688号”,最终找到接收人。

对应地, ARouter 也是个“路由器”,也是个“邮政系统”。ARouter 帮助 无相互依赖的组件间 进行跳转和通信。

抽象一下,邮局、ARouter 都是 路由系统 ——— 给 无依赖的双方 提供 通信和路由的能力。

二、原理解析

使用ARouter在进行Activity跳转非常简单:初始化ARouter、添加注解@Route、发起路由。

1
2
3
// 在module app中
//1.初始化SDK
ARouter.init(mApplication); // 尽可能早,推荐在Application中初始化
1
2
3
4
5
6
7
// moduleA
// 2.在支持路由的页面上添加注解(必选)
// 路径注意至少需有两级,/xx/xx
@Route(path = "/test/activity")
public class YourActivity extend Activity {
    ...
}
1
2
3
// moduleB(没有依赖 moduleA)
// 3.发起路由跳转
ARouter.getInstance().build("/test/activity").navigation();

这样就使得 没有依赖moduleA的moduleB能跳转到moduleA的Activity了。服务获取也是类似简单的代码就可实现。

那么 ARouter 是如何做到只通过简单2步 就完成 解耦组件间的路由操作呢?我们通过源码一步步理解。

2.1 构建PostCard

我们知道 想要跳转Activity最终必定是走到了 startActivity(intent)方法,而intent是一般需要目标Activity的Class。所以我们猜想 navigation()中应该是有寻找目标Activity的Class这一过程的。

下面就来跟踪源码分析这一过程。先看ARouter.getInstance():

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public static ARouter getInstance() {
    if (!hasInit) { // 未初始化则报异常
        throw new InitException("ARouter::Init::Invoke init(context) first!");
    } else {
        if (instance == null) {
            synchronized (ARouter.class) {
                if (instance == null) {
                    instance = new ARouter();
                }
            }
        }
        return instance;
    }
}

获取ARouter单实例,没有初始化则报异常。再看它的build(string)方法:

1
2
3
public Postcard build(String path) {
    return _ARouter.getInstance().build(path);
}

这里是调用了 _ARouter 的同名方法,返回了 Postcard(意为明信片)。ARouter实际是使用了外观模式(设计模式的一种),其所有方法都是调用了_ARouter的同名方法。进入_ARouter:

1
2
3
4
5
6
7
8
9
10
11
12
protected Postcard build(String path) {
    if (TextUtils.isEmpty(path)) { //path不能为空
        throw new HandlerException(Consts.TAG + "Parameter is invalid!");
    } else {
        //path替换,这是预处理
        PathReplaceService pService = ARouter.getInstance().navigation(PathReplaceService.class);
        if (null != pService) {
            path = pService.forString(path);
        }
        return build(path, extractGroup(path), true);
    }
}

这里对path做了空校验和预处理替换。接着又走到重载方法,其中参数group是通过extractGroup(path)获取,也就是path的第一级,即”/test/activity”中的”test”。group的作用是作为路由的默认分组。

最后返回创建的Postcard实例。Postcard是明信片的意思,承载了一次跳转/路由 需要的所有信息,它继承自路由元信息 RouteMeta

  • Postcard:路由的信息。理解为是生活中的明信片。继承自RouteMeta。例如Postcard中的mBundle等则是 明信片上寄件人写的文字内容。
  • RouteMeta:路由元信息,即基础信息。理解就是 明信片上的 收件人地址 这种必备的基础信息。明信片上可以没有文字内容,但想要被邮寄就必须有收件人地址

2.2 路由过程

2.2.1 整体步骤

通过path构建了PostCard后调用了其navigation()方法,也就是开始了路由过程,最后走到_ARouter的navigation方法。使用前面构建好的PostCard经过整体3个步骤,就完成了路由:

  1. 完善postcard信息:只有path、group还不行,还需要知道具体目的地,例如要跳转到的Activity信息。
  2. 拦截器处理:不是绿色通道的话,要先经过路由器的处理,结果是中断或者继续。
  3. 获取路由结果:例如进行目标Activity的跳转、获取IProvider实现对象、获取Fragment等。

2.2.2 获取路由结果

先来看比较简单的最后一个步骤——路由结果获取过程,也就是_navigation()方法。postcard 经过完善后,路由类型type、目的地destination等都已经被赋了值。destination就是目标类的class对象。 方法内容就是根据路由类型来走对应逻辑:

  • Activity,使用postcard.getDestination()来构建intent、传入Extras、设置 flags、action,最后在主线程执行 熟悉的startActivity
  • provider,指的是想要获取的服务,即IProvider的实现类。是直接从postCard中获取的,因为服务类是单例,只会在首次获取时创建。
  • Broadcast、ContentProvider、Fragment,都是使用postcard.getDestination()反射创建实例

这里你可能好奇 destination的值是如何获取的,因为无论哪种类型的路由,都是要使用目标class,这个就是ARouter最为核心的内容——如何获取 无直接依赖的模块的 class对象,也就是完善postcard信息的过程。

2.2.3 拦截器

拦截器模式是开发中常用设计模式之一,路由中也可以设置拦截器,对路径进行判断决定是否需要中断。未设置绿色通道的路由需要经过拦截器处理,也就是interceptorService的doInterceptions()方法。

doInterceptions()方法中判断如果有拦截器,就放入线程池异步执行第一个拦截器,且使用interceptorCounter 保证所有拦截器都走完,同时也设置了超时。如果第一个拦截器没有回调中断 则递归调用继续后面的拦截器。拦截器的执行,是从Warehouse.interceptors中获取第index个拦截器,走process方法,如果回调到onContinue就继续下一个;若回调onInterrupt就中断路由。

InterceptorServiceImpl的init方法会在服务被创建后立即调用,遍历Warehouse.interceptorsIndex ,使用存储在其中的拦截器class对象 反射创建拦截器实例,然后存入 Warehouse.interceptors。也即是说,ARouter初始化完成后就获取到了所有拦截器实例

2.2.4 路由元信息的收集

Warehouse意为仓库,用于存放被 @Route、@Interceptor注释的 路由相关的信息,也就是我们关注的destination等信息。

Warehouse存了哪些信息呢?

  • groupsIndex所有路由组元信息。是所有IRouteGroup实现类的class对象,是在ARouter初始化中赋值,key是path第一级。
  • routes所有路由元信息。是在LogisticsCenter.completion中赋值,key是path。首次进行某个路由时就会加载整个group的路由。
  • providers所有服务provider实例。在LogisticsCenter.completion中赋值,key是IProvider实现类的class。
  • providersIndex所有provider服务元信息。是在ARouter初始化中赋值,key是IProvider实现类的全类名。
  • interceptorsIndex所有拦截器实现类class对象。是在ARouter初始化时收集到,key是优先级。
  • interceptors所有拦截器实例。是在ARouter初始化完成后立即创建。

其中groupsIndex、providersIndex、interceptorsIndex是ARouter初始化时就准备好的基础信息,为业务中随时发起路由操作做好准备。

LogisticsCenter初始化 就是加载所有的路由元信息的过程,有两种方式:

  1. 走loadRouterMap()方法:直接使用在编译时收集好的帮助类信息,然后反射创建帮助类实例后,调用其loadInto方法来填充Warehouse相应的Map。这需要开发者先引入插件 apply plugin: ‘com.alibaba.arouter’。加载成功则registerByPlugin为true。
  2. 若第一步没有加载成功:使用ClassUtils.getFileNameByPackageName在运行时搜集dex中”com.alibaba.android.arouter.routes”包下的所有类(即帮助类),然后遍历,区分是哪种帮助类,反射创建实例后调用loadInto填充Warehouse。

一般都是使用第一种,因为运行时遍历dex搜集会比较耗时,而第一种在编译时已经收集好了

各帮助类:

  • ARouter$$Root$$xxx —— 根帮助类:帮助对 Warehouse.groupsIndex 赋值,把path第一级相同的路由分到同一个组中。目的是避免一次性加载所有路由,减少反射耗时和内存占用。
  • ARouter$$Group$$xxx —— 组帮助类:帮助对 Warehouse.routes 赋值。ARouter的设计是在使用时才进行加载,即首次使用某个组的路由时,才会使用组帮助类对 Warehouse.routes 进行填充。其中最重要的就是 每个路由的目标类class。
  • ARouter$$Interceptors$$xxx —— 帮助填充 Warehouse.interceptorsIndex,即所有拦截器class。
  • ARouter$$Providers$$xxx —— 帮助填充 Warehouse.providersIndex,即所有provider的RouteMeta。

2.2.5 路由信息的完善

completion(Postcard postcard) 的逻辑:

  1. 尝试通过path从仓库中获取对应的路由元信息,如果没有获取到:要么不存在、要么还没有加载本组路由。
  2. 先看路由仓库中是否有这个组帮助类,没就抛出异常;有就通过addRouteGroupDynamic()加载这个组的所有路由,然后再调completion。
  3. 有了path对应的路由元信息,就同步到postCard中,其中最重要的就是 目标类class——routeMeta.getDestination()。并且判断如是provider就创建服务实例并存入仓库。

到这里,我们终于可以解答最开始提出的问题了:ARouter最为核心的内容——如何获取 无直接依赖的模块的 class对象

  • 编译时ARouter根据注解 @Route 生成了各个帮助类,帮助类的loadInto方法中包含了路由目标信息,最重要的是注解的类class;然后在ARouter初始化时根据 根帮助类、provider帮助类、拦截器帮助类 对仓库Warehouse进行赋值;然后在路由发起后,根据path通过Warehouse的groupsIndex 加载 同组的所有路由元信息,也就是拿到了目标class。
  • 抽象一下就是:moduleA先把目标class存入第三方仓库——ARouter的Warehouse,然后moduleB发起路由时从仓库中根据path获取目标class,ARouter就是这个仓库的管理者。就好比 邮政是信件的管理者,它是两方通信者的中间人。

三、总结

我们从路由发起开始,介绍了整个路由详细过程:moduleA通过中间人ARouter把路由信息存到仓库Warehouse;moduleB发起路由时,再通过中间人ARouter从仓库Warehouse取出路由信息,这样就实现了没有依赖的两者之间的跳转与通信。其中涉及Activity的跳转、服务provider的获取、拦截器的处理等。

其中ARouter在编译时生成的帮助类,是用于对所有使用@Route、@Interceptor注解的类信息的分组和收集,这里涉及到的是 Annotation Process ToolAPT)技术,即注解处理工具。如何使用编译时生成的帮助类呢?除了运行时查找dex,还可以在编译时扫描帮助类信息,并且直接在物流中心LogisticsCenter loadRouterMap()方法中直接插入使用帮助类的代码,这里涉及 Android Gradle PluginAGP)技术。

本文由作者按照 CC BY 4.0 进行授权