看懂Arouter路由框架

307 阅读5分钟

1. 基本原理

Arouter是一套开源的路由框架。

在模块化的开发中,不同模块的 Activity如果想要显式的 Intent跳转的话,需要模块之间的互相引用,否则只能隐式Intent跳转。 Arouter实现的就是在模块之间不引用的情况下实现显式的跳转

基本原理就是在每个模块中生成路由的映射关系,等到可以引用再注册到Arouter中,这个时候不同的模块之间已经有了互相引用,互相之间的调用就没了问题,简单说就是将引用延迟

2. 代码生成

Arouter使用APT搭配 JavaPoet生成代码,定义编译期的注解,再通过继承Proccesor实现代码生成逻辑,实现了编译期生成代码的逻辑

路由代码生成逻辑在 RouterProcess文件中,入口 process()中,拿到所有的 Route注解,调用parseRoutes()方法生成代码。

1655280520714.png

2.1. 组装路由元数据

parseRoutes()方法中,第一块是解析 Route注解,组装 RouterMeta路由元数据,以 Activity为类

  1. 遍历 Route注解
  2. 判断是否是 Activty
  3. 解析其中的 AutoWired注解
  4. 单路由组装单个 RouteMeta,多路由组装多个 RouteMeta
  5. 分组缓存 RouteMeta

1655280220168.png

2.2. 分组缓存

将上一步组装的 RouteMeta分组缓存到 groupMap中。

  1. routeVerify()中检测和解析 path,根据分号截取 group
  2. 根据 group分组缓存到 groupMap

1655283093524.png

2.3. 生成分组类

遍历上一步缓存的 groupMap,生成对应 Java文件。

  1. 遍历 AutoWried数据,生成传参相关代码,这里的 Map存储的参数名称以及参数类型的枚举
  2. 将参数信息以及 RouteMeta代码写入 loadInfo()方法
  3. 生成 Java文件,文件名格式 Arouter$$Group$$***,生成的文件包名固定
  4. 将文件名缓存到 rootMap

1655284360888.png

2.4. 生成入口类

生成一个 root类,加载上面生成的分组类。文件名格式 Arouter$$Root$$***

1655285814989.png

1655285829140.png

3. 路由注册

Arouter需要在App启动的时候进行初始化操作,加载前面生成的Java类,注册完成后会启动拦截器,这里没有介绍

3.1. 注册逻辑

路由注册的逻辑 LogisticsCenterinit()方法当中

  1. 获取对应包名的文件,这里的包名就是前面生成的对应包名,如果有缓存的话直接读取缓存
  2. 文件名反射创建对应对象,并根据文件名的不同调用对应方法,初始化 RootInterceptorProvider存入到 Warehouse

1655289355214.png

3.2. 文件遍历

上一步获取对应包名文件的方法 getFileNameByPackageName()

  1. 读取所有的dex文件目录
  2. 启动线程池分别遍历每一个dex
  3. 遍历其中所有的文件,判断是不是对应的包名
  4. 挂起等待所有线程运行完毕

1655290542521.png

3.3. 耗时问题

上面这个方法给人的感觉就是耗时,而且还放在app启动的时候调用,势必会增加启动耗时,官方也发现了这个问题,提供了一个解决方案 arouter-register

arouter-register采用 AutoRegister框架实现字节码插桩,在编译的时候将生成类注入到初始化代码中,启动时不再需要扫描所有文件

  1. 注入的地方是在 LogisticsCenterloadRouterMap(),初始话的时候会首先执行这个方法,然后判断 registerByPlugin的值,如果已经赋值过了,就不会走扫描的逻辑
  2. 注入的方法就是 register(),根据类名去初始化
  3. 最终注册的时候先调用 markRegisterByPlugin()registerByPlugin赋值
  4. 调用创建的类的方法,将路由信息注册到 Warehouse

1655347986562.png

4. 路由跳转

前面的准备工作完成之后,就可以下面的代码实现一个简单的路由的跳转

ARouter.getInstance().build(*****).navigation(context)

4.1. 解析路由

build()方法最终走到 _Arouter中,这里做路由替换逻辑和路由的解析

  1. 获取路由替换的服务,如果有就调用替换路由
  2. 解析路由,用分号分离出分组名称
  3. 创建 Postcard返回

1655363654262.png

4.2. 读取路由

前面成功创建了 Postcard对象,并调用它的 navigation()方法,最终走到了 _Arouter.navigation()方法中,其中第一步的处理就是调用 LogisticsCenter.completion(),根据路由的名称,读取出真实的路由数据

  1. routes中查找缓存,找不到就尝试加载
  2. groupsIndex中查找 group的缓存,前面有提到过这里存储的是生成的分组类信息
  3. 拿到对应类信息之后创建对象并调用方法,将组内所有的页面缓存到 routes
  4. 创建成功后直接再次调用当前方法,走有缓存的逻辑
  5. 这一步之后是成功拿到了缓存的操作,首先将路由信息存入到 postcard
  6. 如果是uri的话进行解析
  7. 如果是 Provider类型,就查对应的缓存,如果没有,创建-初始化-存入缓存
  8. 如果不是 Activity,最终都会调用 greenChannel()方法,跳过拦截器

1655365597470.png

1655365775604.png

4.3. 路由拦截

完成上面的读取路由回到 _Arouter中,会走到下面代码,首先是根据刚才设置的参数判断是否需要拦截器,拦截器回调或者不需要拦截器直接走 _navigation()做路由跳转

1655368231449.png

这里的 interceptorService是内部的维护的拦截控制服务,逻辑在 InterceptorServiceImpl

  1. 创建计数器,防止拦截器没有调用回调,导致部分拦截器没执行
  2. 调用 _excute()加载拦截器执行拦截逻辑
  3. 挂起等待,默认300s(???)
  4. 执行完成后回调给外面
  5. 读取 interceptors缓存,前面提到过在 afterInit()中会初始化拦截器,这里的缓存是所有的拦截器对象
  6. 拦截器的回调是继续走下一个拦截器

1655369532925.png

1655369612978.png

4.4. 路由跳转

拦截器逻辑结束之后,就可以做最终的路由跳转了,回到 _Arouter_navigation()方法

  1. 创建 Intent
  2. 调用跳转代码
  3. 类型是 provider直接返回对象
  4. 类型是 Fragment塞入参数返回对象

1655371022744.png

5. 参考文章

  1. github.com/alibaba/ARo…
  2. www.jianshu.com/p/cdf2649d2…
  3. www.jianshu.com/p/c3aab4db0…