探索 ARouter 原理

首图.png

1. ARouter 原理概述

ARouter 是阿里开源的一款帮助 Android APP 进行组件化改造的路由框架,可以实现在同一个项目中互不依赖的的模块的 Activity 之间跳转。

ARouter 的路由参数拦截器都是用注解来标注的。注解在 Retrofit、Dagger 和 EventBus 中都有使用,注解分为运行时注解编译时注解

ARouter 的跳转是基于路由表 RouterMap 实现的,负责生成路由表的是 RouteProcessor ,负责加载路由表的是 LogisticsCenterRegisterTransform

RouteProcessor 是一个注解处理器,是 AbstractProcessor 的子类。编译时注解是依赖注解处理工具 APT(Annotation Processing Tool)实现的,用于在编译时扫描和处理注解,通过 APT 我们能少写很多模板代码。在编译时,编译器会检查 AbstractProcessor 的子类,并调用 AbstractProcessor 的子类的 process() 方法,然后把添加了注解的元素都传到 process() 方法中,这样我们就可以在 process() 函数中生成新的 Java 类文件。

在 RouteProcessor 的 process() 方法中,会调用 parseRoutes() 方法,parseRoutes() 方法会用 JavaPoet API 来生成 Java 代码,具体的代码就是 Activity 等类的 Class 信息。除了 RouteProcessor ,ARouter 中还有参数注解处理器 AutowiredProcessor 和拦截器注解处理器 InterceptorProcessor ,它们的原理和 RouteProcessor 是一样的。

在 _ARoute 的 navigation() 方法中,首先会处理预处理服务,然后会让 LogisticsCenter 填充 Postcard 中的信息,如果 LogisticsCenter 没有找到对应的路由信息的话,就会走降级策略的逻辑,如果 LogisticsCenter 找到对应的路由信息的话,就会判断是不是走绿色通道,如果不走绿色通道的话就由拦截器链决定要不要跳转。如果走绿色通道的话,就直接按 Fragment 和 Activity 等不同的类型进行跳转。

预处理服务具体就是一个 PretreatmentService 接口,只要定义一个实现了这个接口的类,并给这个类加一个 @Route 注解就可以使用了,预处理服务的作用,是做一些跳转的时候,在加载路由表前的判断。

降级策略的作用是跳转路由的信息缺失的时候,要做的事情,比如说给用户弹一个错误提示或记录错误日志等,降级策略对应的是一个 DegradeService 接口,定义一个实现这个接口的类,并添加上 @Route 注解就可以使用降级策略了。

绿色通道的作用就是判断要不要走拦截器链,比如说我们定义了一个登陆拦截器,但是某个页面不需要做这个判断,就可以走绿色通道,走绿色通道只要在调用 build() 方法后调用 greenChannel() 方法就可以了。

拦截器具体就是一个添加了 @Interceptor 注解并实现了 IInterceptor 接口的类,通过拦截器我们能做一些类似登录态判断等逻辑。

负责生成路由表的是 LogisticsCenterRegisterTransform ,LogisticsCenter 注册路由表的方式是在运行时通过 ClassUtils 加载 dex 文件中的内容,然后再通过反射初始化这些类的信息,并保存到仓库 Warehouse 中。Dex 文件是 Android 平台的可执行文件,类似于 Windows 中的 exec 文件,每个 APK 安装包中都有 dex 文件,dex 文件中包含了 app 的所有源码,反编译后能看到对应分 java 源码。

LogisticCenter 中有一个 loadRouterMap() 方法,这个方法中默认只有一行代码,这行代码就是把 registerByPlugin 字段的值改为 false 。初始化 ARouter 时,会间接调用到 LogisticsCenter 的 init() 方法,在 LogisticsCenter 的 init() 方法中,会判断是否由插件加载路由表,如果不是的话,就在运行时,通过 ClassUtils 从 Dex 文件中加载路由表的信息。如果是由 Gradle 插件来注册路由表的话,那么就由 RegisterTransform 从 Jar 文件中读取路由表的信息。 RegisterTransform 继承了 Transform 类,Transform 是 Android 官方提供的用来修改 class 文件的 API ,每个 Transform 都是一个 Gradle 任务,能读取和处理 jar、aar 和 resource 等资源,用户自定义的 Transform 会插在 Transform 队列的最前面。

Transform API 可以做很多的事情,比如在所有的 class 文件中插桩,做 UI 、内存和网络方面的性能监控。还可以修改某个第三方库的 class 文件,修改它的逻辑。还可以在 Log 中插入当前代码行数,这样更容易定位问题。还可以对任何类进行动态代理。还可以实现打印出某个方法的入参和出参的代码。

2. ARouter 简介

ARouter 是阿里开源的一款帮助 Android App 进行组件化改造的路由框架,是 Android 平台中对页面和服务提供路由功能的中间件,可以实现在不同模块的 Activity 之间跳转。ARouter 的特点是灵活性强还能帮助项目解耦

灵活性强指的是在一些复杂的业务场景下,很多功能都是运营人员动态配置的。比如电商系统需要下发一个活动页面,App 事先不知道该活动具体的目标页面,但如果提前做好了页面映射,就可以自由配置了。

帮助解耦指的是,随着业务量增长,我们项目代码会越来越多,开发人员之间的协作也会变得越来越复杂,而解决这个问题的常见方案是插件化和组件化。插件化和组件化的前提是代码解耦,解耦后还要保持页面之间的依赖关系,这时就要一套路由机制了。

ARouter 支持直接解析标准 URL 跳转多模块工程使用添加多个拦截器单独作为依赖注入框架配置转场动画生成路由文档等多个特性。

3. ARouter 架构概览

ARouter 架构-路由表.png

ARouter 项目中包含了 API编译器 Compiler插件 Gradle Plugin注解 Annotation 4 个模块。

API 模块由launchercoreexceptionthreadfacedeutilsbase子模块组成。

launcher,包含了启动器 ARouter,core 包含物流中心 LogsticsCenter仓库 Warehouse 等类。exception 包含了一些异常类。thread 包中包含了CancellableCountDownLatch,ARRouter 的拦截器链是放在子线程中执行的,就用到了它。facede 模块包含了导航回调 NavigationCallbackIInterceptor 等接口。utils 包含了 ARouter 自定义的日志打印器等工具类。base 包下只有一个用于保存拦截器的 UnitqueKeyTreeMap

ARouter 的 Compiler 模块包含用于生成路由表的类,@Autowired@Interceptor@Route 注解对应的注解处理器分别是 AutowiredProcessor、InterceptorProcessor 以及 RouteProcessor 等注解处理器都在 Compiler 包中。

Register Plugin 模块包含了注册代码生成器 RegisterCodeGenerator 和 RegisterTransform,如果我们使用了 ARouter 的路由表加载插件,那这个路由表就会由 Register 插件加载。

Annotation 模块只包含了一些 @Autowired 等注解和路由类型 RouteType 等枚举类。

4. ARouter 基本用法

接下来的代码会统一用 Kotlin 来演示。

1. 添加依赖与配置
添加依赖.png
2. 声明路径

使用 ARouter 要用 @Route 注解声明跳转目标的路径,在这里要注意,最前面的斜杠是不能少的,而且路径至少有两级。

group 是可选的,ARouter 内部会对 path 进行分组,以下面这段代码为例,如果不传 group 的话,那 ARouter 会把 goods 作为该路径的 group ,否则 taobao 就是该路径所属的 group 。

声明路径.png
3. 初始化 ARouter

打印日志和开启调试模式必须写 在init() 之前,否则这些配置在初始化过程中将无效。

initARouter.png
4. 跳转
跳转1.png

在跳转时,我们也可以传 group,比如这个例子中传了 taobao,那就会跳转到 taobao 分组下的商品详情页,不传的话,就会跳到 goods 分组下的详情页。

5. 明信片 Postcard

在讲 ARouter 的路由表生成原理前,我们先来看下在路由表生成和加载过程中非常重要的 RouteMetaPostcard 。当我们调用 ARouter.getinstance().build() 时,其实是在创建一个明信片 Postcard 对象,withXXX() 和 navigation() 等方法就是它的方法,Postcard 的 navigation() 方法最终调用的是 _ARouter 的 navigation() 方法,关于 _ARouter 的实现后面会讲。

按 Postcard 的注释来说,它是路线图的容器,我们可以把它看做是一张包含了收件人信息以及特定内容的明信片。

Postcard 继承了 RouteMeta,RouteMeta 是路由表的内容,而 Postcard 则包含了在跳转时的传参和动画等信息,我们先来看下 RouteMeta。

5.1 RouteMate

RouteMeta 包含了跳转路线的基本信息,从名字上来看就是路线的元信息,元信息也就是关于信息的信息,RouteMeta 包含了以下字段。

  • 路线类型 type
  • 路线原始类型 rawType
  • 终点 destination
  • 路径 path
  • 路线组 group
  • 优先级 int
  • 标志 extra
  • 参数类型 paramsType
  • 路线名 name
  • 注入配置 injectConfig
1. 路线类型

路线类型 RouteType 是一个枚举类,有下面几种类型。

  • Activity

  • Service

  • Provider

    也就是自定义服务 IProvider;

  • ContentProvider

  • Fragment

  • Broadcast

  • Method

  • Unknown

    未知路线;

其中 Service、Broadcast 和 Method 是未实现的。

2. 路线原始类型

路由原始类型 rawType 的类型为 Element,关于 Element ,在后面会进一步讲解,这里只需要知道 Element 中包含了跳转目标的 Class 信息,是由路由处理器 RouteProcessor 设定的。

3. 终点

终点 destination 就是声明了 @Route 的跳转目标的 Class ,比如目标 Activity 和 Fragment 的 Class,这个信息也是由 RouteProcessor 设定的。

4. 路径和路线组

如果我们在 @Route 中只设定了路径,比如 path = /goods/details ,那么 goods 就是 group ,details 就是路径 path 。

如果我们设置了 @Route 的 group 的值,比如 group = taobao ,那么 path 的 group 就是 taobao。

5. 优先级

优先级在 @Route 中无法设定,是给拦截器用的,priority 的值越小,拦截器的优先级就越高。

拦截器链的实现类为 InterceptorServiceImpl,它在调用拦截器的 onInteceptor() 方法时,就是按照 priority 的顺序来获取拦截器,然后逐个调用的。

6. 标志

这个 extra 是路由文档 RouteDoc 的标志,文档中包含了路由和服务相关的信息 ,默认不会生成对应的文件,如果想生成的话,可以在注解处理参数中设置生成文档。

  • 添加参数

    如果我们想查看路由文档,可以 AROUTER_MODULE_NAME 后面添加:

    AROUTER_GENERATE_DOC: "enable"

  • 文档路径

    build/generated/source/apt/(debug or release)/com/alibaba/android/arouter/docs/arouter-map-of-${moduleName}.json

路由文档的大致内容如下。

路由文档.png
7. 参数类型

paramsType 也就是参数类型,对于我们跳转时设定的参数,ARouter 会根据不同的类型给它们一个枚举值,然后取值时,再根据不同的类型调用 Intent 的 getXXXExtra() 等方法。

5.2 Postcard

Postcard.png

Postcard 中包含如下字段。

  • 统一资源标识符 uri
  • 标签 tag
  • 传参 mBundle
  • 标志 flags
  • 超时时间 timeout
  • 自定义服务 provider
  • 是否走绿色通道 greenChannel
  • 序列化服务 serializationService
  • 转场动画 optionsCompat
  • 进入与退出动画 enterAnim/exitAnim

下面我们来看下这些字段的作用。

1. uri

在 ARouter 中,我们可以用 URI 作为路径跳转,ARouter 会用路径替换服务 PathReplaceService 替换路径,这个服务没有默认实现,如果我们需要为不同的协议和主机替换不同的路径时,就要自己实现这个服务。

uri 跳转.png

如果我们没有实现这个服务,那 scheme 和 主机地址等信息是没有意义的,ARouter 默认情况下只会对路径和参数进行解析,在上面的例子中,路径就是 moduleA/second ,参数为 name=老王。

2. tag
使用 tag.png

tag 用于在 NavigationCallback 的 interrupt() 方法中获取异常信息。

在拦截器链 InterceptorServiceImpl 中,当我们在自定义的拦截器中调用 onInterrupt() 方法时,InterceptorServiceImpl 会创建一个 HandlerException ,并把它作为 Postcard 的 tag ,然后监听跳转结果的地方,就能在 onInterrupt() 方法中通过获取 Postcard 的 tag 信息来获取异常信息。

我们在拦截器中传入的 exception 为空的话,那么 HandlerException() 的 message 默认为 "No Message." 。

3. mBundle

我们调用 withString() 等方法设置要传递给跳转目标的数据时,这个数据就是放在 mBundle 中的。

4. flags

我们调用 withFlag() 设定 Activity 的启动标志时,这个标志就会赋值给 flags 字段。

5. timeout

拦截器链处理跳转事件是放在 CountDownLatch 中执行的,超时时间默认为 300 秒,也就是 5 分钟,所以我们在拦截器中不要进行时间太长的耗时操作。

6. provider

当我们实现了自定义服务时,参数注解处理器 AutowiredProcessor 会为各个路径创建一个实现注射器 ISyringe 接口的类,在这个类的 inject() 方法中,调用了 ARouter.getInstance().navigation(XXXService.class) ,当 LogisticsCenter 发现这是一个 Provider 时,就会通过反射创建一个 Provider 实例,然后设置给 Postcard ,再进行跳转。

7. greenChannel

所谓绿色通道,就是不会被拦截器链处理的通道,自定义服务 IProvider 和 Fragment 就是走的绿色通道。

如果我们想让某个跳转操作跳过拦截器,可以在 navigation() 前调用 greenChannel() 方法。

8. serializationService

当我们调用 withObject() 方法时,ARouter 就会获取我们自己自定义的序列化服务 SerializationService,然后调用该服务的 object2Json() 方法,再把数据转化为 String 放入 bundle 中。

6. ARouter 路由表生成原理

看完了 RouteMeta 和 Postcard,接下来我们来看下 RouteProcessor 的路由表生成流程。

6.1 注解处理流程

注解处理流程.png

关于 Android Gradle 的 和 Javac 这里不会展开讲,在这里我们只需要理解在对 @Route 等注解处理前,是由 Android Gradle 的 AndroidJavaCompiler 来发起编译操作,然后让 Javac 去调用 RouteProcessor 等注解处理器对注解进行处理的。

6.2 路由解析流程

RouterProcessor.process().png

RouteProcessor 对于声明了 @Route 注解的类的处理大致可分为下面 4 个步骤

  1. 获取路由元素
  2. 创建路由元信息
  3. 把路由元信息进行分组
  4. 生成路由文件
1. 获取路由元素

这里的元素指的是 javax 包中的 Element ,Element 表示 Java 语言元素,比如字段、包、方法、类以及接口。

process() 方法会接收到 annotations 和 roundEnv 两个参数,annotations 就是当前处理器要处理的注解,roundEnv 就是运行环境 JavacRoundEnvironment。

JavacRoundEnvironment 有一个 getElementsAnnotatedWith() 方法,RouteProcessor 在处理注解时首先会用这个方法获取元素。

2. 创建路由元信息
按类型创建路由元信息.png

这里说的路由元信息指的是 RouteMeta,RouteProcessor 会把声明了 @Route 注解的的 Activity、Provider、Service 或 Fragment 和一个 RouteMeta 关联起来。

当元素类型为 Activity 时,RouteProcessor 会遍历获取 Activity 的子元素,也就是 Activity 的成员变量,把它们放入注入配置 RouteMeta 的 injectConfig 中,当我们配置了要生成路由文档时,RouteProcessor 就会把 injectConfig 写入到文档中,然后就对元信息进行分组。

3. 把路由元信息进行分组

在 RouteProcessor 中有一个 groupMap,在 RouteMeta 创建好后,RouteProcessor 会把不同的 RouteMeta 进行分组,放入到 groupMap 中。

拿路径 /goods/details 来说,如果我们在 @Route 中没有设置 group 的值,那么 RouteProcessor 就会把 goods 作为 RouteMeta 的 group 。

4. 生成路由表
生成路由文件.png

当 RouteProcessor 把 RouteMeta 分组好后,就会用 JavaPoet 生成 Group、Provider 和 Root 路由文件,路由表就是由这些文件组成的,JavaPoet 是 Square 开源的代码生成框架。

生成路由文件后,物流中心 LogisticsCenter 需要用这些文件来填充仓库 Warehouse 中的 routes 和 providerIndex 等索引,然后在跳转时根据 routes 和索引来跳转。

关于 LogisticsCenter 和 Warehouse 在后面会讲,下面我们来看下路由文件的内容。

RouteProcessor 生成的路由文件位于build/generated/source/kapt/(debug/release)/com/alibaba/android/arouter/routes。

以 ARouter 示例项目中的一个路由文件为例,RouteProcessor 创建的 RouteMeta 会转化为下面这样的文件。

ARouterGroupTest.png

其他的路由文件也是大同小异的,所有内容都是由 RouteProcessor 填充的。

7. ARouter 跳转原理

看完了路由表生成流程,下面我们来看下 ARouter 的跳转原理。

当我们调用 Postcard 的 navigation() 方法时,Postcard 会调用 _ARouter 的 navigation() 方法,然后 _ARouter 才会去加载路由表,下面看下 navigation() 的处理流程。

_ARouter 的 navigation() 方法有下面两种重载。

navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback)
  
navigation(Class<? extends T> service)

7.1 navigation()

第一种重载是进行跳转的,第二种重载是用来创建服务的,下面我们来看第一种重载的实现。

_ARouter.navigation().png

_ARouter 的 navigation() 方法的大致处理流程如下。

_ARouter 的 navigation() 首先会根据我们实现的预处理服务,判断是否继续往下处理,不往下处理则中断跳转流程。

如果预处理服务返回的是 true ,那么 navigation() 方法就会加载路由表,把 RouteMeta 的信息填充到 Postcard 中,比如终点 destination 等信息,这个操作是由物流中心 LogisticsCenter 做的。

假如 LogisticsCenter 在完善明信片的过程中遇到了异常,比如找不到路径对应的目标,那么就会调用降级策略,我们可以在降级策略中显示错误提示等信息。

  1. 拦截器链

    在明信片完善信息后,navigation() 就会把跳转事件交给拦截器链处理;

  2. 按类型跳转

    在拦截器链处理完成,并且没有中断跳转时,navigation() 就会按照路径类型跳转到不同的页面或调用自定义服务;

1. 实现预处理服务

预处理服务可以让我们在 ARouter 进行跳转前,根据 PostCard 的内容判断是否要独立地对这次跳转进行处理,是的话则在 onPretreatment() 中返回 false 即可。

预处理服务.png
2. 预处理服务处理流程

在 navigation() 中,首先会调用预处理服务的 onPretreamtn() 方法,判断是否要继续往下处理,如果返回结果为 false ,则不再往下处理,也就是不会进行跳转等操作。

7.2 完善明信片

调用完预处理服务后,_ARouter 就会用物流中心 LogisticsCenter 来加载路由表,路由表也就是 RouteProcessor 生成的路由文件。

1. 获取路由元信息

在 _ARouter 初始化时,会把 LogisticsCenter 也进行初始化,而 LogisticsCenter 的初始化方法中,会读取 RouteProcessor 创建好的路由表,然后放到对应的索引 index 中。

有了索引,当 _ARouter 调用 LogisticsCenter 的 completion() 方法时,就可以用索引从 Warehouse 的 routes 中获取路由元信息。

如果 LogisticsCenter 根据索引查找不到对应的 RouteMeta,那就说明 routes 还没有被填充,这时 LogisticsCenter 就会获取 group 的 RouteMeta,然后把 group 下的路径填充到 routes 中,然后再调用一次 completion() ,这时就可以取填充明信片的信息了。

索引是在 LogsticsCenter 初始化的时候加载的,后面在路由表加载原理的时候会讲到。

2. 填充明信片信息

我们在调用 ARouter.getInstance().build() 方法时,就是创建了一张 Postcard,但是这张 Postcard 的信息是不完整的。

比如 Postcard 中有一个 destination 字段,destination 表示跳转目标的 Class 信息,跳转 Activity 要用 Intent,destination 就是 Intent 的构造函数的第二个参数。

而 LogisticsCenter 就要负责把从路由表获取到的 destination 信息填充到这张明信片中,有了一张信息完整的明信片,才能进行后续的跳转操作。

3. 初始化 Provider

填充完了 Postcard 的信息后,LogisticsCenter 会根据 Postcard 的类型来做不同的操作,如果是 Provider 的话,就会调用 Provider 的初始化方法,并且把 Postcard 设为绿色通道。

如果是 Fragment 的话,那就只把 Postcard 设为绿色通道,如果是其他类型,则不设为绿色通道,这里说的绿色通道,其实就是说 Provider 和 Fragment 是跳过拦截器链的。

2.1.3 降级策略

所谓的降级策略,其实就是跳转失败时,我们能够跳转到别的页面,比如一个跳转失败提示页。

假如 ARouter 在完善明信片信息的过程中遇到了异常,

1. 降级策略处理流程
降级策略.png

调用完预处理服务后,navigation() 就会通过物流中心 LogisticsCenter 来填充 PostCard 内容,如果在填充过程中遇到了异常,就会调用降级服务,关于 LogisticsCenter 后面会讲。

2. 自定义降级策略

在自定义降级策略时,要注意 context 可能会空,要使用 Context? 。

7.3 拦截器

拦截器可以用来在跳转过程中处理事件,比如做登陆检查,拦截器会在跳转之间执行,多个拦截器会按优先级顺序依次执行。

1. 实现拦截器

实现拦截器时,我们要调用 onContinue() 或 onInterrupt() 方法,至少需要调用其中一种方法,否则不会继续路由。

onContinue() 这个方法表示处理完成,交换控制权给 ARouter;

如果不想继续跳转,可以用 onInterrupt() 方法,传一个异常,以中断路由流程;

onInterrupt().png
2. 拦截器处理流程

navigation() 在完善 Postcard 信息后,就会判断该 Postcard 是否通过绿色通道来处理,绿色通道就是不经过拦截器链的通道,关于拦截器链的实现在后面会讲。

在 LogisticsCenter 的 completion() 中,中会把路由类型为 Provider 和 Fragment 的路线设为绿色通道,如果我们想让目标页面跳过拦截器链,就可以在 navigation() 方法前调用 greenChannel() 方法。

7.4 按类型跳转

_navigation().png

当处理完拦截器后,navigation() 中就会调用 _navigation() 方法,这也是具体进行跳转的方法。

在这个方法中,会根据 Postcard 的路由类型 RouteType 来判断怎么跳转,

1. Activity

当 RouteType 为 Activity 时,启动 Activity 的流程和我们平时启动 Activity 的流程是一样的,创建 Intent、传入 destination、设置 fragment 和重写动画等,最终调用 startActivity() 启动目标页面。

2. Fragment / Broadcast / ContentProvider

当跳转目标为 Fragment、Broadcast 或 ContentProvider 时,会通过 destination 用反射创建实例,如果是 Framgent ARouter 还会为它设置要传递给目标 Fragment 的参数,然后返回实例。

3. Provider

如果跳转目标为 Provider,也就是自定义服务的话,就对应了后面讲 ARouter 自定义服务时讲的“通过依赖查找发现服务”。

7.5 跳转回调

跳转事件触发时机.png

在调用 navigation() 跳转时,我们可以在 navigation() 中传入 NavigationCallback 监听跳转过程中发生的事件。

  • onLost()

    无法查找到跳转目标;

  • onFound()

    找到了跳转目标;

  • onInterrupt()

    拦截器中断了跳转;

  • onArrival()

    已打开跳转目标;

NavigationCallback.png

除了 NavigationCallback ,我们也可以用 NavCallback 监听跳转中发生的事件,NavCallback 是 ARouter 中实现了 NavigationCallback 的一个抽象类,使用 NavCallback 不会强制要求我们重写所有方法,只要求重写 onArrival() 方法。

NavCallback.png

8. ARouter 路由表加载原理

所谓的加载路由表,其实就是加载 RouteProcessor 生成的类文件

在我们调用 ARouter 的 init() 方法时,ARouter 会调用 LogisticsCenter 的 init() 方法,在 LogisticsCenter 的 init() 方法中,会判断当前路由表加载方式是否为插件,不是的话则从 Dex 中加载路由表,是的话则由插件从 Jar 中加载路由表

下面我们来看下怎么通过 Dex 加载路由表。

8.1 从 Dex 中加载路由表

通过 Dex 加载路由表.png

通过类加载路由表的流程大致可分为读取 Dex 文件从 Dex 文件中读取路由表把路由表保存到本地以及把路由信息保存到索引这 4 步。

读取 Dex 文件,指的是当 LogisticsCenter 发现没有用插件加载路由表时,就会用 ClassUtils 读取路由表。这里说的路由表,其实就是 RouteProcessor 生成好的类文件类名,而读取 Dex 文件的方式,就是从源码目录(applicationInfo.sourceDir) 中读取 base apk 的路径,然后用这个路径构建一个 DexFile(path) 。

从 Dex 文件中读取路由表指的是 ClassUtils 会用 DexFile 读取 apk 中的类信息,然后判断类的包名是否为 com.alibaba.android.arouter.routes,是的话说明这是 ARouter 的注解处理器生成的路由文件,把匹配上的类加入列表中,然后把列表返回给 LogisticsCenter 。

把路由表保存到本地指的是当 LogisticsCenter 从 ClassUtils 中获取到注解处理器生成的类名时,就会把这些类名保存 SharedPreferences 中,下次就根据 App 版本判断,如果不是新版本,就从本地中加载类名,否则就用 ClassUtils 读取类名。

把路由信息保存到索引指的是当 LogisticsCenter 把路由表保存到 SharedPreferences 后,就会根据类名的后缀判断类是 IRouteRoot 、IInterceptorGroup 还是 IProviderGroup ,然后根据不同的类把类文件的内容加载到索引中。

8.2 从 Jar 中加载路由表

如果我们想缩短 ARouter 的初始化时间,可以用 ARouter 的 Gradle 插件,这个插件能自动加载路由表,这样 ARouter 初始化的时候就不需要读取类的信息,从而缩短初始化时间。

应用插件.png

Register 插件从 Jar 文件加载路由表的流程如下。

插件加载路由表流程.png

当我们运行 App 时,Gradle 就会调用我们依赖的 ARouter Register 插件,这个插件的执行的起点就在 PluginLaunch 的 apply() 中,PluginLaunch 判断了只有在运行的项目为 Applicaiton 时,才会加载路由表信息。

Android Gradle 插件包含了一个 Transform API,这个 API 允许第三方插件在编译后的类文件转换为 dex 文件前做处理操作,而 ARouter 的 Register 插件就实现了一个 RegisterTransform

当 Gradle 的任务管理器 TaskManager 执行 TransformTask 时,就会执行 RegisterTransform 的 transform() 方法。在 transform() 方法中,会接收到一个 TaskManager 传过来的 TransformInput 集合,通过 TransformInput 可以获取到 Gradle 为我们项目生成的 Jar 和 Class 文件,包括 RouteProcessor 生成的文件

当 RegisterTransform 获取到了 Class 文件后,就会用 ScanUtils 中的 ScanClassVisitor 访问 Class 中的字节码数据,包括 Class 的包名和类名。ScanClassVisitor 是 ClassVisitor 的子类,ClassVisitor 可以改变修改字节码,当 ScanClassVisitor 发现当前 Class 实现了 IRouteRootIIntercetorGroupIProviderGroup 接口时,这三个接口都是就会把当前 Class 的类名添加到 ScanSetting 的 classList 中。

当 RegisterTransform 把 ScanSetting 中的 classList 初始化完后,就会用代码生成器 RegisterCodeGenerator 插入代码到 Jar 文件中。

在 RegisterCodeGenerator 的 insertInitCodeIntoJarFile() 方法中,会把 Jar 文件转化为 JarFile 对象,然后获取到 JarFile 中的 JarEntry ,找到 LogisticsCenter,然后把路由表插入到 LogisticsCenter 的 loadRouteMap() 方法中,比如下面这样。

插入路由表.png
  • Jar 文件路径

    build/intermediates/transforms/com.alibaba.arouter/debug/41.jar

9. ARouter 进阶用法与注意事项

1. 传参
跳转2.png
2. 解析参数
解析参数.png

在使用 @Autowired 注解时,要注意下面几点。

这里需要注意的是,如果 Activity 或 Fragment 是用 Kotlin 写的,那字段就要加上 @JvmField ,以提供 getter()setter() 给 ARouter 使用。

key 不能为空,因为会被取做默认值,否则会出现空指针异常。因为 ARouter 在用 Intent 的 getLongExtra() 等方法获取参数时,这些方法默认值不是包装类型,如 Long ,而是基本类型,如 long 。

使用 withObject() 传递 List 和 Map 时,接收该对象的地方不能标注具体的实现类类型,应声明为 List 或 Map,否则会影响序列化中类型的判断, 其他类似情况需要同样处理。

如果你发现接收不到你想要的参数时,可以在 AutowiredServiceImplautowired() 方法中打个断点看下遇到了什么异常。

如果在使用 ARouter 的过程中,出现了编译失败但是不知道原因时,可以用命令查看详细的编译日志 gradle aseembleDebug --stacktrace

3. 实现序列化服务

ARouter 允许我们设置序列化服务,如果需要传递自定义对象,那只需要新建一个实现 SerializationService 的类,并加上 @Route 注解 。我们可以给服务设定不同的分组,就像下面的 yourservicegroupname 一样。

序列化服务.png
4. 自定义服务

除了序列化等服务以外,ARouter 还允许我们通过实现 IProvider 接口实现自定义服务。

首先声明接口。

自定义服务.png

然后实现接口。

ARouter 发现服务的方式有两种,一是通过依赖注入发现服务,二是通过依赖查找发现服务,推荐通过依赖注入发现服务,比如下面这样。

通过依赖注入发现服务.png

helloService 和 helloService2 就是通过依赖注入的方式发现服务的,也就通过注解标注字段即可使用,无需主动获取。

如果不设置 name 属性的话,会默认使用 byType 的方式发现服务,helloService 就是 byType。

Autowired 注解中标注 name 之后,将会使用 byName 的方式注入对应的字段。

当同一接口有多个实现的时候,必须使用byName的方式发现服务,helloService2 就是 byName。

下面是通过依赖查找发现服务的代码。主动发现服务并使用,helloService3 是 byName ,helloService4 是 byType 。

通过依赖查找发现服务.png
5. 通过 URL 跳转

如果我们想让应用直接处理外部 URI,只要在清单文件中进行配置,再把 url 传递给 ARouter 即可。

首先设定 host,在 Manifest 中声明 Activity 的 action 和 category ,并且在 data 标签中添加主机 host协议 scheme

清单.png

然后新建一个 Activit y用于监听 Scheme 事件,直接把 url 传递给 ARouter 即可。

监听 scheme.png
6. 设置转场动画

ARouter 提供了 withTransition() 方法来设置跳转的转场动画

转场动画.png
7. 获取 Fragment

获取 Fragment 时要注意,当找不到对应路径的 Fragment 时,会返回 null,所以这里用空安全的 as? 。

获取 Fragment.png

10. 其他

如果你想交流 Android 开发相关的问题,欢迎加我的微信 oushaoze2015 一起探讨,添加时请备注“掘金”。

参考资料

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容