当前位置: 首页 > news >正文

路由框架 ARouter 原理及源码解析

文章目录

  • 前言
  • 一、ARouter 简介
  • 二、ARouter 使用
    • 1.添加依赖和配置
    • 2.添加注解
    • 3.初始化SDK
    • 4.发起路由操作
  • 三、ARouter 成员
    • 1. PostCard 明信片
    • 2. Interceptor 拦截器
    • 3. Warehouse 路由仓库
    • 4. ARouter 注解处理
  • 四、ARouter 原理
  • 五、ARouter 源码分析
    • 1. ARouter 初始化
      • 1.1 ARouter#init()
      • 1.2 _ARouter#init()
      • 1.3 LogisticsCenter#init()
      • 1.4 ClassUtils#getFileNameByPackageName()
    • 2. ARouter 路由跳转
      • 2.1 ARouter.getInstance()
      • 2.2 ARouter#build()
      • 2.3 _ARouter#build()
      • 2.3 _ARouter#navigation()
        • 2.3.1 LogisticsCenter#completion()
        • 2.3.2 _ARouter#_navigation()
    • 3. ARouter 获取 Service
  • 总结
    • 1. ARouter 初始化
    • 2. ARouter 路由跳转
  • ARouter 思考
  • 参考


前言

在日常开发中,随着项目业务越来越复杂,项目中的代码量也越来越多,如何维护、扩展、解耦等成了一个非常头疼问题。为解决此问题而衍生出的诸如:插件化组件化模块化等热门技术。 使用组件化来改造项目时的难点,就是实现各个组件之间的通讯,通常解决方案采用路由中间件,来实现页面之间的跳转关系。本文要解析的 ARouter 路由框架就是众多解决方案中比较优秀的一个开源库,并且是国人团队开发的,所以中文文档非常详细,以便使用者快速接入。


一、ARouter 简介

ARouter 是阿里开源的一款用于帮助 Android App 进行组件化改造的路由框架,支持模块间的路由、通信、解耦,是 Android 平台中对页面、服务提供路由功能的中间件,以实现在不同模块的 Activity 之间跳转。

ARouter 功能介绍:

  1. 支持直接解析标准URL进行跳转,并自动注入参数到目标页面中
  2. 支持多模块工程使用
  3. 支持添加多个拦截器,自定义拦截顺序
  4. 支持依赖注入,可单独作为依赖注入框架使用
  5. 支持InstantRun
  6. 支持MultiDex(Google方案)
  7. 映射关系按组分类、多级管理,按需初始化
  8. 支持用户指定全局降级与局部降级策略
  9. 页面、拦截器、服务等组件均自动注册到框架
  10. 支持多种方式配置转场动画
  11. 支持获取 Fragment
  12. 完全支持 Kotlin 以及混编
  13. 支持第三方 App 加固(使用 arouter-register 实现自动注册)
  14. 支持生成路由文档
  15. 提供 IDE 插件便捷的关联路径和目标类
  16. 支持增量编译(开启文档生成后无法增量编译)
  17. 支持动态注册路由信息

ARouter 的典型应用场景有:

  1. 从外部URL映射到内部页面,以及参数传递与解析
  2. 跨模块页面跳转,模块间解耦
  3. 拦截跳转过程,处理登陆、埋点等逻辑
  4. 跨模块API调用,通过控制反转来做组件解耦

ARouter 工作原理:
ARouter 工作原理
ARouter 架构:

ARouter 源码架构

  • appARouter 提供的一个测试 Demo
  • arouter-annotation:模块中声明了很多注解信息和一些枚举类,如:供业务侧使用的注解 @Route 等
  • arouter-api:框架的核心逻辑实现,并提供便于业务侧使用的 ARouter 的核心 api
  • arouter-compiler:定义注解处理器,用于生 Java 类,即用来自动生成路由表
  • arouter-gradle-plugin:编译期使用的 Plugin 插件,主要作用是用于编译器自动加载路由表,节省应用的启动时间

ARouter 架构图


二、ARouter 使用

1.添加依赖和配置

android {defaultConfig {...javaCompileOptions {annotationProcessorOptions {// ARouter参数arguments = [AROUTER_MODULE_NAME: project.getName()]}}}
}dependencies {// 替换成最新版本, 需要注意的是 api 要与 compiler 匹配使用,均使用最新版可以保证兼容compile 'com.alibaba:arouter-api:1.2.4'annotationProcessor 'com.alibaba:arouter-compiler:1.2.4'...
}

2.添加注解

// 在支持路由的页面上添加注解(必选)
// 这里的路径需要注意的是至少需要有两级:/xx/xx
@Route(path = "/first/activity")
public class FirstActivity extends AppCompatActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_first);......}
}

3.初始化SDK

if (BuildConfig.DEBUG) {    // 这两行必须写在init之前,否则这些配置在init过程中将无效ARouter.openLog();      // 打印日志ARouter.openDebug();    // 开启调试模式(如果在InstantRun模式下运行,必须开启调试模式!线上版本需要关闭,否则有安全风险)
}
ARouter.init(mApplication); // 尽可能早,推荐在Application中初始化

4.发起路由操作

public class MainActivity extends AppCompatActivity implements View.OnClickListener {@Overridepublic void onClick(View v) {if (v.getId() == R.id.first_button) {// 1. 应用内简单的跳转ARouter.getInstance().build("/first/activity").navigation();// 2. 跳转并携带参数ARouter.getInstance().build("/test/1").withLong("key1", 666L).withString("key3", "888").withObject("key4", new Test("Jack", "Rose")).navigation();}}
}

三、ARouter 成员

1. PostCard 明信片

public final class Postcard extends RouteMeta {// Base	private Uri uri;				// 使用 Uri 方式发起路由private Object tag;             // A tag prepare for some thing wrong. inner params, DO NOT USE!private Bundle mBundle;         // 需要传递的参数使用 Bundle 存储private int flags = 0;          // 启动 Activity 的标志,如:NEW_FALGprivate int timeout = 300;      // 路由超时时间private IProvider provider;     // 使用 IProvider 的方式跳转private boolean greenChannel;	// 绿色通道,可以不经过拦截器// 序列化服务serializationService:需要传递Object自定义类型对象,就需要实现这个服务private SerializationService serializationService; private Context context;        // 在使用应用程序或活动之前,需检查实例类型private String action;			// Activity 跳转的 Action// Animationprivate Bundle optionsCompat;   // Activity 的过渡动画private int enterAnim = -1;private int exitAnim = -1;......
}

Postcard 明信片,跟我们去各大旅游景点购买然后寄给朋友的明信片是一样的,包含需要传递的参数、跳转方式等等,PostCard 继承自 RouteMeta,来看看其包含哪些重要参数:

public class RouteMeta {private RouteType type;         // 路由类型:如Activity,Fragment,Provider等private Element rawType;        // 路由原始类型,在编译时用来判断private Class<?> destination;   // 目标 Class 对象private String path;            // 路由注册的 pathprivate String group;           // 路由注册的 group 分组private int priority = -1;      // 路由执行优先级,priority 越低,优先级越高,这个一般在拦截器中使用private int extra;              // 额外数据private Map<String, Integer> paramsType;  // 参数类型,例如 Activity 中使用 @Autowired 的参数类型private String name;			// 路由名字,用于生成 javadoc   // 参数配置(对应paramsType).private Map<String, Autowired> injectConfig;  // 缓存注入配置(对应paramsType)......
}

RouteMeta 主要存储的是一些目标对象的信息,这些对象是在路由注册的时候才会生成。

2. Interceptor 拦截器

ARouter 中存在一套拦截器机制,所有的路由调用在 completion 的过程中都需经过自定义的一系列拦截器,实现一些 AOP 切面编程。因此先来看看其拦截器机制的实现,首先看一下 IInterceptor 接口类:

public interface IInterceptor extends IProvider {/*** The operation of this interceptor.** @param postcard meta* @param callback cb*/void process(Postcard postcard, InterceptorCallback callback);
}

IInterceptor 继承了 IProvider,所以其也是一个服务类型,只需要实现 process() 方法就可以实现拦截操作,在其内部对 Postcard 进行处理,拦截器的执行是通过 InterceptorServiceImpl#doInterceptions() 方法实现的:

@Route(path = "/arouter/service/interceptor")
public class InterceptorServiceImpl implements InterceptorService {private static boolean interceptorHasInit;private static final Object interceptorInitLock = new Object();@Overridepublic void doInterceptions(final Postcard postcard, final InterceptorCallback callback) {if (MapUtils.isNotEmpty(Warehouse.interceptorsIndex)) {// 检查拦截器 IInterceptor 的初始化状态,如还未初始化完成,则等待 10s,如仍未初始化完成则报错checkInterceptorsInitStatus(); if (!interceptorHasInit) { // 回调通知拦截器初始化耗时太多异常callback.onInterrupt(new HandlerException("Interceptors initialization takes too much time."));return;}LogisticsCenter.executor.execute(new Runnable() {@Overridepublic void run() {// 构建值为 interceptors 个数的 CountDownLatch 计数器,用于倒数计数,子线程每执行成功一个// 调用 CountDownLatch.countDown() 方法,interceptors 的个数减 1,直到 interceptors 的个数减为 0// CountDownLatch.await() 就会自动解除等待状态, 不再阻塞主线程, 进入运行状态CancelableCountDownLatch interceptorCounter = new CancelableCountDownLatch(Warehouse.interceptors.size());try {// 调用 _execute() 方法执行一个拦截器的 iInterceptor.process() 方法_execute(0, interceptorCounter, postcard);interceptorCounter.await(postcard.getTimeout(), TimeUnit.SECONDS);if (interceptorCounter.getCount() > 0) {    // Cancel the navigation this time, if it hasn't return anythings.callback.onInterrupt(new HandlerException("The interceptor processing timed out."));} else if (null != postcard.getTag()) {    // Maybe some exception in the tag.callback.onInterrupt((Throwable) postcard.getTag());} else {callback.onContinue(postcard);}} catch (Exception e) {callback.onInterrupt(e);}}});} else {callback.onContinue(postcard);}}/*** Excute interceptor** @param index    current interceptor index* @param counter  interceptor counter* @param postcard routeMeta*/private static void _execute(final int index, final CancelableCountDownLatch counter, final Postcard postcard) {if (index < Warehouse.interceptors.size()) {// 获取 index 对应的拦截器 IInterceptorIInterceptor iInterceptor = Warehouse.interceptors.get(index); // 调用拦截器 IInterceptor#process() 方法执行拦截操作,对 Postcard 进行处理iInterceptor.process(postcard, new InterceptorCallback() {@Overridepublic void onContinue(Postcard postcard) {// Last interceptor excute over with no exception.counter.countDown(); // 计数器 CountDownLatch 的 interceptors 的个数减 1// 继续调用 _execute() 方法,只是 index 的数值 + 1,来执行下一个拦截器的 iInterceptor.process() 方法_execute(index + 1, counter, postcard);  // When counter is down, it will be execute continue ,but index bigger than interceptors size, then U know.}@Overridepublic void onInterrupt(Throwable exception) {// Last interceptor execute over with fatal exception.// save the exception message for backup.postcard.setTag(null == exception ? new HandlerException("No message.") : exception);   counter.cancel();}});}}......
}

InterceptorServiceImpl#doInterceptions() 方法中,通过 ThreadPoolExecutor 线程池和 CountDownLatch 计数器,实现对每个拦截器 IInterceptor 调用其 process() 方法执行拦截操作,对 Postcard 进行处理。

3. Warehouse 路由仓库

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

class Warehouse {// 保存所有 IRouteGroup 实现类的class对象,在 ARouter 初始化中赋值,key 是 path 第一级//(IRouteGroup 实现类是编译时生成,代表一个组,即path第一级相同的所有路由,包括Activity和Provider服务)static Map<String, Class<? extends IRouteGroup>> groupsIndex = new HashMap<>();// 保存所有路由元信息 RouteMeta,是在 completion 中赋值,key是path// 首次进行某个路由时就会加载整个group的路由,即IRouteGroup实现类中所有路由信息。包括Activity和Provider服务static Map<String, RouteMeta> routes = new HashMap<>();// 保存所有服务provider实例,在completion中赋值,key是IProvider实现类的classstatic Map<Class, IProvider> providers = new HashMap<>();// 保存所有provider服务的元信息(实现类的class对象),在 ARouter 初始化中赋值,key是IProvider实现类的全类名// 主要用于使用IProvider实现类的class发起的获取服务的路由,例如ARouter.getInstance().navigation(HelloService.class)static Map<String, RouteMeta> providersIndex = new HashMap<>();// 保存所有拦截器实现类的class对象,在 ARouter 初始化时收集到,key是优先级 static Map<Integer, Class<? extends IInterceptor>> interceptorsIndex = new UniqueKeyTreeMap<>("More than one interceptors use same priority [%s]");// 保存所有拦截器,在 ARouter 初始化完成后立即创建static List<IInterceptor> interceptors = new ArrayList<>();......
}

其中 groupsIndexprovidersIndexinterceptorsIndexARouter 初始化时就准备好的基础信息,为业务中随时发起路由操作(Activity 跳转、服务获取、拦截器处理)做好准备。

4. ARouter 注解处理

ARouter 通过注解处理器 AnnotationProcessor 配合 AutoService 来实现的,并通过 JavaPoet 实现对 Java 文件的编写。

AnnotationProcessor:注解处理器是一种工具,它通过检索源代码中的注解信息,执行特定的代码生成任务或对代码进行检查。ARouter 使用注解处理器在源码编译的阶段,通过 APT 获取被 @Route@Interceptor 等注解的路由相关信息,在对应模块下动态下动态生成 .java 源文件,通常是自动产生一些有规律性的重复代码,解决了手工编写重复代码的问题,大大提升编码效率。详细可以看看 arouter-complier 包下的具体实现。

AutoService:是 Google 开发的一个自动生成 SPI(全称是 Service Provider Interface) 清单文件的框架,用来自动帮我们注册 APT 文件(全称是 Annotation Process Tool,或者叫注解处理器,AbstractProcessor 的实现类)。

JavaPoetJava 诗人,名字是不是很优雅,JavaPoet 是由 Square 推出的开源 Java 代码生成框架,能够提供 Java 生成源代码文件的能力,通过这种自动化生成代码的方式,可以让我们用更加简洁优雅的方式替代掉繁琐冗杂的重复工作。

SPIService Provider Interface 的简称,是 JDK 默认提供的一种将接口和实现类进行分离的机制。这种机制能将接口和实现进行解耦,大大提升系统的可扩展性。

SPI 机制约定:当一个 Jar 包需要提供一个接口的实现类时,该 Jar 包需要在 META-INF/services 目录里同时创建一个以服务接口命名的文件,该文件里就是实现该服务接口的具体实现类。而当外部程序装配这个模块的时候,就能通过该 JarMETA-INF/services/ 里的配置文件找到具体的实现类名,并装载实例化,完成模块的注入。


四、ARouter 原理

ARouter 工作原理主要包括:路由表生成、路由匹配和页面跳转三个方面:

  • ARouter 通过注解处理器来生成路由表,在每个组件的 build.gradle 文件中,配置注解处理器的依赖和配置信息,在编译时,注解处理器会扫描项目中所有使用了 @Route 注解的类,然后根据注解中的信息生成一个路由表。该路由表包含了每个被注解标记的类对应的路由信息,如:路径、组件名、优先级等;
  • ARouter 在运行时会根据路由表来进行路由匹配,当需要跳转到某个页面时,通过调用 ARouter.getInstance().build(path) 方法来获取一个路由构建器,构建器中的 path 参数对应了要跳转的页面路径。随后 ARouter 根据该路径在路由表中进行匹配,找到对应的 Class 对象并返回;
  • ARouter 通过反射来进行页面跳转,当找到目标 Class 对象后,调用 navigation() 方法进行页面跳转,ARouter 自动调用目标页面的构造方法来创建一个实例,并且会根据传递的参数来进行参数的注入(ARouter.withString() 方法)。 然后 navigation() 方法的内部调用 startActivity(intent) 方法进行页面跳转,至此便实现两个相互没有依赖的 module 顺利的启动对方的 Activity 的目标。

五、ARouter 源码分析

1. ARouter 初始化

ARouter 在使用前需要通过调用 ARouter#init() 方法并传入 Application 进行初始化:

1.1 ARouter#init()

public final class ARouter {private volatile static ARouter instance = null;private volatile static boolean hasInit = false;public static ILogger logger;/*** Init, it must be call before used router.*/public static void init(Application application) {if (!hasInit) {logger = _ARouter.logger;_ARouter.logger.info(Consts.TAG, "ARouter init start.");// 继续调用 _ARouter.init() 函数进行初始化hasInit = _ARouter.init(application);if (hasInit) {// ARouter 创建了一个 InterceptorServiceImpl 服务的实例对象,后面讲到拦截器的时候会用到_ARouter.afterInit();}_ARouter.logger.info(Consts.TAG, "ARouter init over.");}}
}

继续调用 _ARouter#init() 函数进行初始化:

1.2 _ARouter#init()

final class _ARouter {static ILogger logger = new DefaultLogger(Consts.TAG);private volatile static _ARouter instance = null;private volatile static boolean hasInit = false;private volatile static ThreadPoolExecutor executor = DefaultPoolExecutor.getInstance();private static Handler mHandler;private static Context mContext;protected static synchronized boolean init(Application application) {mContext = application;LogisticsCenter.init(mContext, executor);logger.info(Consts.TAG, "ARouter init success!");hasInit = true;mHandler = new Handler(Looper.getMainLooper());return true;}
}

内部初始化并赋值一些 mContextmHandler 以及字段信息,最重要的是调用 LogisticsCenter#init(mContext, executor) 函数来初始化。

1.3 LogisticsCenter#init()

public class LogisticsCenter {private static Context mContext;static ThreadPoolExecutor executor;private static boolean registerByPlugin;/*** LogisticsCenter init, load all metas in memory. Demand initialization*/public synchronized static void init(Context context, ThreadPoolExecutor tpe) throws HandlerException {mContext = context;executor = tpe;try {long startInit = System.currentTimeMillis();// 首先使用 AGP 插件进行路由表的自动加载(要先引入插件才行 apply plugin: 'com.alibaba.arouter')loadRouterMap();if (registerByPlugin) { // 如果 registerByPlugin 被设置为true,说明使用的是插件加载,直接跳过logger.info(TAG, "Load router map by arouter-auto-register plugin.");} else { // loadRouterMap() 中设置 registerByPlugin 为 false,因此调用下面步骤加载Set<String> routerMap;// 如果是 debug 模式或者是新版本的,则每次都会去加载 routerMap,这会是一个耗时操作if (ARouter.debuggable() || PackageUtils.isNewVersion(context)) {logger.info(TAG, "Run with debug mode or new install, rebuild router map.");// 获取 arouter-compiler 模块生成的存储 ClassName 集合的 routerMap// 根据指定的 packageName 获取 package 下的所有 ClassNamerouterMap = ClassUtils.getFileNameByPackageName(mContext, ROUTE_ROOT_PAKCAGE);if (!routerMap.isEmpty()) {// 存入 SP 缓存中context.getSharedPreferences(AROUTER_SP_CACHE_KEY, Context.MODE_PRIVATE).edit().putStringSet(AROUTER_SP_KEY_MAP, routerMap).apply();}PackageUtils.updateVersion(context);    // 路由表更新完成后保存新版本名} else {// 如果是其他的情况,如:release 模式下,已经缓存了 ClassName 列表,则直接去文件中读取logger.info(TAG, "Load router map from cache.");routerMap = new HashSet<>(context.getSharedPreferences(AROUTER_SP_CACHE_KEY, Context.MODE_PRIVATE).getStringSet(AROUTER_SP_KEY_MAP, new HashSet<String>()));}logger.info(TAG, "Find router map finished, map size = " + routerMap.size() + ", cost " + (System.currentTimeMillis() - startInit) + " ms.");startInit = System.currentTimeMillis();// 遍历 routerMap 获取 ClassNamefor (String className : routerMap) {// 如果className = "com.alibaba.android.arouter.routes.ARouter$$Root"格式// 则加载类构建对象后通过 loadInto() 方法将路由组信息添加到 Warehouse.groupsIndex 中if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_ROOT)) {// This one of root elements, load root.((IRouteRoot) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.groupsIndex);} else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_INTERCEPTORS)) {// 如果className = "com.alibaba.android.arouter.routes.ARouter$$Interceptors"格式// 则加载类构建对象后通过 loadInto() 方法将拦截器信息添加到 Warehouse.interceptorsIndex 中((IInterceptorGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.interceptorsIndex);} else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_PROVIDERS)) {// 如果className = "com.alibaba.android.arouter.routes.ARouter$$Providers"格式// 则加载类构建对象后通过 loadInto() 方法将服务 Provider 信息添加到 Warehouse.providersIndex 中((IProviderGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.providersIndex);}}}logger.info(TAG, "Load root element finished, cost " + (System.currentTimeMillis() - startInit) + " ms.");if (Warehouse.groupsIndex.size() == 0) {logger.error(TAG, "No mapping files were found, check your configuration please!");}......} catch (Exception e) {throw new HandlerException(TAG + "ARouter init logistics center exception! [" + e.getMessage() + "]");}}
}

LogisticsCenter#init() 函数执行流程如下:

  • 获取 com.alibaba.android.arouter.routes 下存储 ClassName 的集合 routerMap。如果是 debug 模式或者是新版本的,则每次都会调用 ClassUtils#getFileNameByPackageName() 函数根据指定的 packageName 获取 package 下的所有 ClassName 并保存到集合 routerMap 中;如果不是 debug 模式且之前已经解析过,则直接从 SP 中读取(已有缓存)。注意debug 模式每次都需要更新,因为类会随着代码的修改而变动。
  • 遍历 routerMap 中的 ClassName,如果是 RouteRoot,则加载类构建对象后通过 loadInto() 方法将路由组信息添加到 Warehouse.groupsIndex 中;如果是 InterceptorGroup,则加载类构建对象后通过 loadInto() 方法将拦截器信息添加到 Warehouse.interceptorsIndex 中;如果是 ProviderGroup,则加载类构建对象后通过 loadInto() 方法将服务 Provider 信息添加到 Warehouse.providersIndex 中。

1.4 ClassUtils#getFileNameByPackageName()

public class ClassUtils {private static final String EXTRACTED_NAME_EXT = ".classes";private static final String EXTRACTED_SUFFIX = ".zip";/*** 通过指定包名,扫描包下面包含的所有的ClassName** @param context     U know* @param packageName 包名* @return 所有class的集合*/public static Set<String> getFileNameByPackageName(Context context, final String packageName) throws PackageManager.NameNotFoundException, IOException, InterruptedException {final Set<String> classNames = new HashSet<>();// 通过 getSourcePaths 方法获取 dex 文件 path 集合List<String> paths = getSourcePaths(context);// 通过 CountDownLatch 对 path 的遍历处理进行控制final CountDownLatch parserCtl = new CountDownLatch(paths.size());// 遍历 path,通过 DefaultPoolExecutor 并发对 path 进行处理for (final String path : paths) {DefaultPoolExecutor.getInstance().execute(new Runnable() {@Overridepublic void run() {DexFile dexfile = null; // 加载 path 对应的 dex 文件try {if (path.endsWith(EXTRACTED_SUFFIX)) {// NOT use new DexFile(path), because it will throw "permission error in /data/dalvik-cache"// 如果是 .zip 结尾的文件通过 DexFile.loadDex 进行加载dexfile = DexFile.loadDex(path, path + ".tmp", 0);} else {// 否则通过 new DexFile 加载dexfile = new DexFile(path);}Enumeration<String> dexEntries = dexfile.entries();// 遍历 dex 中的 Entrywhile (dexEntries.hasMoreElements()) {// 如果是对应的 package 下的类,则添加到 classNames 中String className = dexEntries.nextElement();if (className.startsWith(packageName)) {classNames.add(className);}}} catch (Throwable ignore) {Log.e("ARouter", "Scan map file in dex files made error.", ignore);} finally {if (null != dexfile) {try {dexfile.close();} catch (Throwable ignore) {}}// 计数器 CountDownLatch 的 paths 的个数减 1parserCtl.countDown();}}});}// 所有 path 处理完成后,CountDownLatch.await() 自动解除等待状态, 不再阻塞主线程, 进入运行状态继续向下走流程parserCtl.await();Log.d(Consts.TAG, "Filter " + classNames.size() + " classes by packageName <" + packageName + ">");return classNames;}
}

ClassUtils#getFileNameByPackageName() 函数的执行流程如下:

  • 通过 getSourcePaths() 方法获取 dex 文件的 path 集合;
  • 创建了一个 CountDownLatch 计数器控制 dex 文件的并行处理,以加快速度;
  • 遍历 path 列表,通过 DefaultPoolExecutor 线程池对 path 并行处理;
  • 加载 path 对应的 dex 文件,并对其内部的 Entry 进行遍历,若发现了对应 package 下的 ClassName,将其加入结果集合 classNames 中。

流程至此,ARouter 初始化过程就完成了对自动生成的路由相关类 RouteRootInterceptorProviderGroup 的加载,并对它们通过反射构造后将信息加载进了 Warehouse 类中。

2. ARouter 路由跳转

以第二节 ARouter 用法中的发起路由操作的代码为例:

// 1. 应用内简单的跳转
ARouter.getInstance().build("/first/activity").navigation();

首先看一下 ARouter#getInstance() 函数获取 ARouter 实例对象:

2.1 ARouter.getInstance()

public final class ARouter {private volatile static ARouter instance = null;private volatile static boolean hasInit = false;private ARouter() {}    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 是否已经初始化,如果已经初始化则直接新建 ARouter 实例对象。

2.2 ARouter#build()

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

_ARouter#getInstance() 函数跟 ARouter 的类似,不再继续贴代码,继续转调 _ARouter 的同名 build() 方法:

2.3 _ARouter#build()

final class _ARouter {/*** Build postcard by path and default group*/protected Postcard build(String path) {if (TextUtils.isEmpty(path)) {throw new HandlerException(Consts.TAG + "Parameter is invalid!");} else {// 获取 PathReplaceService 接口的实例,该接口需要用户来实现,若没有实现则返回 null// 若有实现则调用其 forString() 方法传入用户的 RoutePath 进行路径的预处理PathReplaceService pService = ARouter.getInstance().navigation(PathReplaceService.class);if (null != pService) {path = pService.forString(path);}// 通过 extractGroup() 方法对字符串 path 进行截取处理,取出 Route Group 的名称部分// 然后继续调用重载的 build() 方法,传入路径 path、Route Group 的名称部分return build(path, extractGroup(path), true);}}/*** Build postcard by path and group*/protected Postcard build(String path, String group, Boolean afterReplace) {if (TextUtils.isEmpty(path) || TextUtils.isEmpty(group)) {throw new HandlerException(Consts.TAG + "Parameter is invalid!");} else {if (!afterReplace) {PathReplaceService pService = ARouter.getInstance().navigation(PathReplaceService.class);if (null != pService) {path = pService.forString(path);}}return new Postcard(path, group);}}
}

_ARouter#build() 的重载方法中,由于入参 afterReplacetrue,因此根据入参 pathgroup 直接新建 Postcard 实例对象并返回。

2.3 _ARouter#navigation()

通过上面的分析可知,ARouter#navigation() 方法,最终也是委托给 _ARouter#navigation() 进行处理的,因此不再贴 ARouter#navigation() 方法的代码,直接看 _ARouter#navigation() 方法的实现:

final class _ARouter {/*** Use router navigation.** @param context     Activity or null.* @param postcard    Route metas* @param requestCode RequestCode* @param callback    cb*/protected Object navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {PretreatmentService pretreatmentService = ARouter.getInstance().navigation(PretreatmentService.class);if (null != pretreatmentService && !pretreatmentService.onPretreatment(context, postcard)) {return null; // 预处理失败,取消页面跳转}// 如果没有传入 Context,则使用 ARouter 初始化时传入的 Application 作为 Contextpostcard.setContext(null == context ? mContext : context);try {// 通过 LogisticsCenter.completion() 方法对 postcard 进行补全//(目前只有path、group,还需要知道具体目的地,例如要跳转到的activity信息)LogisticsCenter.completion(postcard);} catch (NoRouteFoundException ex) {logger.warning(Consts.TAG, ex.getMessage());if (debuggable()) {// Show friendly tips for user.runInMainThread(new Runnable() {@Overridepublic void run() {Toast.makeText(mContext, "There's no route matched!\n" +" Path = [" + postcard.getPath() + "]\n" +" Group = [" + postcard.getGroup() + "]", Toast.LENGTH_LONG).show();}});}// 通过 NavigationCallback 对 navigation 的过程进行监听if (null != callback) {callback.onLost(postcard);} else {// No callback for this invoke, then we use the global degrade service.DegradeService degradeService = ARouter.getInstance().navigation(DegradeService.class);if (null != degradeService) {degradeService.onLost(context, postcard);}}return null;}if (null != callback) {callback.onFound(postcard);}// 如果设置了 greenChannel,会跳过所有拦截器的执行if (!postcard.isGreenChannel()) {   // It must be run in async thread, maybe interceptor cost too mush time made ANR.// 调用 InterceptorService.doInterceptions() 方法,对 postcard 的所有拦截器进行执行interceptorService.doInterceptions(postcard, new InterceptorCallback() {/*** Continue process* @param postcard route meta*/@Overridepublic void onContinue(Postcard postcard) {_navigation(postcard, requestCode, callback);}/*** Interrupt process, pipeline will be destory when this method called.* @param exception Reson of interrupt.*/@Overridepublic void onInterrupt(Throwable exception) {if (null != callback) {callback.onInterrupt(postcard);}logger.info(Consts.TAG, "Navigation failed, termination by interceptor : " + exception.getMessage());}});} else {return _navigation(postcard, requestCode, callback);}return null;}
}

_ARouter#navigation() 方法的执行流程如下:

  • 通过 LogisticsCenter#completion() 方法对 Postcard 进行补全;
  • 如果 Postcard 没有设置 greenChannel,则对 Postcard 的拦截器进行执行,执行完成后调用 _navigation 方法真正实现跳转;如果设置了 greenChannel,即绿色免检通道,则直接跳过所有拦截器,直接调用 _navigation 方法来跳转。
2.3.1 LogisticsCenter#completion()
public class LogisticsCenter {public synchronized static void completion(Postcard postcard) {if (null == postcard) {throw new NoRouteFoundException(TAG + "No postcard!");}// 通过 Warehouse.routes.get 由 postcard.path 路径尝试获取 RouteMetaRouteMeta routeMeta = Warehouse.routes.get(postcard.getPath());if (null == routeMeta) { // 若 routeMeta 为 null,可能是并不存在,或是还没有加载进来if (!Warehouse.groupsIndex.containsKey(postcard.getGroup())) {throw new NoRouteFoundException(TAG + "There is no route match the path [" + postcard.getPath() + "], in group [" + postcard.getGroup() + "]");} else {// 加载路由并将其缓存到内存中,然后 routeMeta 中删除try {// 调用 addRouteGroupDynamic() 方法,如果 Warehouse.groupsIndex 中包含 postcard.getGroup(),但是还未加载// 则将其移除后,重新 loadInto 进来addRouteGroupDynamic(postcard.getGroup(), null);if (ARouter.debuggable()) {logger.debug(TAG, String.format(Locale.getDefault(), "The group [%s] has already been loaded, trigger by [%s]", postcard.getGroup(), postcard.getPath()));}} catch (Exception e) {throw new HandlerException(TAG + "Fatal exception when loading group meta. [" + e.getMessage() + "]");}// 重新调用 completion() 方法对其进行补全completion(postcard);}} else {// 如果找到了对应的 routeMeta,将它的信息设置进 postcard 中postcard.setDestination(routeMeta.getDestination());postcard.setType(routeMeta.getType());postcard.setPriority(routeMeta.getPriority());postcard.setExtra(routeMeta.getExtra());Uri rawUri = postcard.getUri();if (null != rawUri) { // 将获取到的 uri 中的参数设置进 bundle 中Map<String, String> resultMap = TextUtils.splitQueryParameters(rawUri);Map<String, Integer> paramsType = routeMeta.getParamsType();if (MapUtils.isNotEmpty(paramsType)) {// 针对由 @Param 注释的参数,按其类型设置值for (Map.Entry<String, Integer> params : paramsType.entrySet()) {setValue(postcard,params.getValue(),params.getKey(),resultMap.get(params.getKey()));}// Save params name which need auto inject. 保存需要自动注入的参数名postcard.getExtras().putStringArray(ARouter.AUTO_INJECT, paramsType.keySet().toArray(new String[]{}));}// Save raw uripostcard.withString(ARouter.RAW_URI, rawUri.toString());}// 对 provider 和 fragment,进行特殊处理switch (routeMeta.getType()) {case PROVIDER:  // if the route is provider, should find its instance// 如果是一个 provider,尝试从 Warehouse 中查找它的类并构造对象,然后将其设置到 providerClass<? extends IProvider> providerMeta = (Class<? extends IProvider>) routeMeta.getDestination();IProvider instance = Warehouse.providers.get(providerMeta);if (null == instance) { // 如果没有找到 provider 的实例对象IProvider provider;try { // 新建 provider 实例对象并初始化,然后加入到 Warehouse.providers 中provider = providerMeta.getConstructor().newInstance();provider.init(mContext);Warehouse.providers.put(providerMeta, provider);instance = provider;} catch (Exception e) {logger.error(TAG, "Init provider failed!", e);throw new HandlerException("Init provider failed!");}}postcard.setProvider(instance);postcard.greenChannel();    // Provider 需要跳过所有的拦截器,因此需设置 greenChannel 为 truebreak;case FRAGMENT:postcard.greenChannel();    // Fragment 也不需要拦截器,因此设置 greenChannel 为 truedefault:break;}}}
}

LogisticsCenter#completion() 的执行流程如下:

  • 通过 Warehouse.routes.get 由 path 路径尝试获取 RouteMeta 对象;
  • 若获取不到 RouteMeta 对象,可能是不存在或是还没有进行加载(第一次都未加载),尝试获取 RouteGroup 调用其 loadInto 方法将 RouteMeta 加载进 Warehouse.routes,最后调用 completion 重新尝试补全;
  • 若获取到 RouteMeta 对象,则将 RouteMeta 的信息设置到 postcard 中,其中会将 rawUri 的参数设置进 Bundle
  • 对于 ProviderFragment 特殊处理,其中 Provider 会从 Warehouse 中加载并构造它的对象,然后设置到 postcard注意ProviderFragment 都会跳过拦截器。
2.3.2 _ARouter#_navigation()
final class _ARouter {private Object _navigation(final Postcard postcard, final int requestCode, final NavigationCallback callback) {final Context currentContext = postcard.getContext();switch (postcard.getType()) { // 根据 postcard 的 type 来分别处理case ACTIVITY:// 对 Activity,构造 Intent,将参数设置进去final Intent intent = new Intent(currentContext, postcard.getDestination());intent.putExtras(postcard.getExtras());// 设置 flagsint flags = postcard.getFlags();if (0 != flags) {intent.setFlags(flags);}// 不是 activity,需要设置 FLAG_ACTIVITY_NEW_TASK,新建一个栈if (!(currentContext instanceof Activity)) {intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);}// 设置 ActionsString action = postcard.getAction();if (!TextUtils.isEmpty(action)) {intent.setAction(action);}// 切换到主线程,调用 startActivity() 方法,启动新的页面runInMainThread(new Runnable() {@Overridepublic void run() {startActivity(requestCode, currentContext, intent, postcard, callback);}});break;case PROVIDER:// provider 类型的直接返回对应的 providerreturn postcard.getProvider();case BOARDCAST:case CONTENT_PROVIDER:case FRAGMENT:// 对于 broadcast、contentprovider、fragment,构造对象,设置参数后返回Class<?> fragmentMeta = postcard.getDestination();try {Object instance = fragmentMeta.getConstructor().newInstance();if (instance instanceof Fragment) {((Fragment) instance).setArguments(postcard.getExtras());} else if (instance instanceof android.support.v4.app.Fragment) {((android.support.v4.app.Fragment) instance).setArguments(postcard.getExtras());}return instance;} catch (Exception ex) {logger.error(Consts.TAG, "Fetch fragment instance error, " + TextUtils.formatStackTrace(ex.getStackTrace()));}case METHOD:case SERVICE:default:return null;}return null;}
}

_ARouter#_navigation() 函数的执行流程如下:

  • 对于 Activity,会构造一个 Intent 并将之前 postcard 中的参数设置进去,之后会切换到主线程,调用 startActivity() 方法,启动新的页面;
  • 对于 Provider,直接返回其对应的 provider 对象;
  • 对于 BroadcastContentProviderFragment,反射构造对象后,将参数设置进去并返回。

通过分析 ARouter 的初始化和路由跳转的整体逻辑,可以发现其实整体还是不复杂的,实际上就是对 ActivityFragment 的调转过程进行了包装。

3. ARouter 获取 Service

ARouter 除了可以通过 ARouter#getInstance()#build()#navigation() 这样的方式实现页面跳转之外,还可以通过ARouter#getInstance()#navigation(XXService.class) 这样的方式实现跨越组件的服务获取,来看看其是如何实现的:

public <T> T navigation(Class<? extends T> service) {return _ARouter.getInstance().navigation(service);
}

ARouter 还是委托给 _ARouter 来实现:

final class _ARouter {protected <T> T navigation(Class<? extends T> service) {try {// 通过 LogisticsCenter.buildProvider 传入 service.class 的 name 构建一个 postcardPostcard postcard = LogisticsCenter.buildProvider(service.getName());// Compatible 1.0.5 compiler sdk.// 早期版本没有使用完全限定名来获取服务if (null == postcard) {// 没有服务,或者这个服务是旧版本,因此再次通过 LogisticsCenter.buildProvider // 传入 simpleName 来构建 postcardpostcard = LogisticsCenter.buildProvider(service.getSimpleName());}if (null == postcard) {return null; // 仍然获取不到则返回 null}// Set application to postcard.postcard.setContext(mContext);// 对 postcard 进行补全,前面分析过LogisticsCenter.completion(postcard);// 通过 postcard.getProvider 获取对应的 Provider 并返回return (T) postcard.getProvider();} catch (NoRouteFoundException ex) {logger.warning(Consts.TAG, ex.getMessage());return null;}}
}

_ARouter#navigation() 函数,首先通过 LogisticsCenter#buildProvider() 方法传入 service.class 的类名 name 构建出一个 postcard。如果获取不到,则再次通过 LogisticsCenter#buildProvider() 方法传入 simpleName 来构建出一个 postcard。随后通过 LogisticsCenter#completion() 方法对 postcard 进行补全,最后通过 postcard#getProvider() 方法获取对应的 Provider 实例对象。


总结

1. ARouter 初始化

  1. 遍历 Apkdex 文件,查找存放自动生成类的包下的类 ClassName 集合。其中为了加快查找速度,通过一个线程池 DefaultPoolExecutor 进行异步查找,并通过 CountDownLatch 来等待所有异步查找任务的结束。这个查找过程在非 debug 模式下是有缓存的,因为 releaseApk 其自动生成的类信息必然不会变 化;
  2. 根据 ClassName 的类型,分别构建 RouteRootInterceptorGroupProviderGroup 的对象并调用了其 loadInto() 方法将这些 Group 的信息装载进 Warehouse,这个过程并不会将具体的 RouteMeta 装载。这些 Group 中主要包含了一些其对应的下一级的信息(如:RouteGroupClass 对象等),之后就只需要取出下一级的信息并从中装载,不再需要遍历 dex 文件。

2. ARouter 路由跳转

ARouter 路由调用时序图
结合 ARouter 路由跳转时序图,对 ARouter 的路由过程做个总结:

  1. 通过 ARouter 中的 build(path) 方法构建出一个 Postcard,或直接通过其 navigate(serviceClass) 方法构建一个 Postcard
  2. 通过 Postcard 中提供的一系列方法对这次路由进行配置,包括携带的参数,是否跳过拦截器等;
  3. 通过 ARouter#navigation() 方法完成路由的跳转,其步骤如下:
    - 通过 LogisticsCenter#completion() 方法根据 Postcard 的信息结合 Warehouse 中加载的信息对 PostcardDestinationType 等信息进行补全,这个过程中会实现对 RouteMeta 信息的装载,并且对于未跳过拦截器的类会逐个调用拦截器进行拦截器处理;
    - 根据补全后 Postcard 的具体类型,调用对应的方法进行路由的过程(如:对于 Activity 调用其 startActivity() 方法,对于 Fragment 构建对象并调用其 setArgument() 方法)。

ARouter 思考

问题ARouter 有没有什么可优化点?或者说有没有缺点?
:任何框架或多或少都会有这样那样的问题存在,也都在一点点儿优化更新,ARouter 路由框架也有缺点,那就是耗时较多,尤其是对于大型 App 来说,在某些低端 Android 机型影响更大。

  • 首先,通过本文的分析可知,在初始化阶段要生成路由对照表(group,Class<? extends IRouteGroup>),需要遍历 dex 文件,如果是 .zip 文件还需要调用 DexFile#loadDex(path, path + “.tmp”, 0) 方法先获取到 dex 文件,在拿到所有包名为:com.alibaba.android.arouter.routes 的路径 path,最后通过 SharedPreferences 将获取到的路径和版本号存储起来,以便下次获取。虽说同版本第二次运行时不会再进行遍历,但依然要调用 SharedPreferences 去获取之前存储的路由表数据,这也是一个耗时操作。
  • 其次,需根据包名调用反射将对照表缓存到一个单例类 Warehouse 中,此时缓存中已经有了 Map<String, Class<? extends IRouteGroup>> routes,其存储了 group 和 ARouter$ $Group$ $.class 的一个映射表。运行时通过 navigate 方法路由导航的时候,依然需要由 group 拿到了 Root$$*** 类,然后用反射创建它的实例,调用它的方法将 pathRouteMeta (里面存储了 Activity 等信息)缓存起来。

以上这些原因是因为,每个 Module 打包时编译产物是相互隔离的,分别生成 Module 内的映射类,因此运行时,需要将各个 Module 生成的类进行汇总,生成总的映射表,但这个生成过程也是很耗时的,会影响到 App 的启动时长,给 App 体验造成负面的影响。那么,如何优化它呢?

优化方案
可以通过 ARouter 提供的注册插件 (power by AutoRegister) 进行路由表的自动加载,通过 Gradle 插件进行自动注册可以缩短初始化时间解决应用加固导致无法直接访问 dex 文件。通过判断如果使用了插件,在编译期就自动注册,完成注册表信息的存储,避免了扫描 dex 文件这种耗时的操作。

需要注意的是,该插件必须搭配 api 1.3.0 以上版本使用!

AutoRegister:基于字节码插桩,在 Android 中实现跨 module 自动注册的 Gradle 插件,可用于模块解耦。使用此插件后,在编译期(代码混淆之前)扫描所有打到 apk 包中的类,将符合条件的类收集起来,并生成注册代码到指定的类的 static 块中,自动完成注册。

参考

github.com/alibaba/ARouter

相关文章:

  • Linux-目录和文件
  • Blazor的SSR服务端渲染是不是交互式的
  • 数据结构:3.3.4遍历应用例子
  • 达索系统基于模型的系统工程:开启创新与高效的新时代
  • Golang | Leetcode Golang题解之第145题二叉树的后序遍历
  • vmware workstation下centos7屏幕切换及大小调整
  • 计算机专业毕设-在线商城系统
  • 【源码】2024最新陪诊小程序uniapp+thinkphp
  • 在WordPress中使用AI的实用方法:入门级
  • 【名词解释】Unity中的3D物理系统:刚体
  • 企业级-封装Java对内卷PDF利用关键字分页导出标题
  • shell脚本监控docker容器和supervisor 运行情况
  • python-docx顺序读取word内容
  • 服务器主机托管服务内容科普
  • vscode连接ssh远程服务器
  • [ 一起学React系列 -- 8 ] React中的文件上传
  • 【140天】尚学堂高淇Java300集视频精华笔记(86-87)
  • AHK 中 = 和 == 等比较运算符的用法
  • Android框架之Volley
  • Asm.js的简单介绍
  • CAP 一致性协议及应用解析
  • ES6系列(二)变量的解构赋值
  • Idea+maven+scala构建包并在spark on yarn 运行
  • k个最大的数及变种小结
  • passportjs 源码分析
  • UMLCHINA 首席专家潘加宇鼎力推荐
  • 等保2.0 | 几维安全发布等保检测、等保加固专版 加速企业等保合规
  • 构建二叉树进行数值数组的去重及优化
  • 利用阿里云 OSS 搭建私有 Docker 仓库
  • 试着探索高并发下的系统架构面貌
  • 提升用户体验的利器——使用Vue-Occupy实现占位效果
  • python最赚钱的4个方向,你最心动的是哪个?
  • ​520就是要宠粉,你的心头书我买单
  • ​flutter 代码混淆
  • ​软考-高级-系统架构设计师教程(清华第2版)【第9章 软件可靠性基础知识(P320~344)-思维导图】​
  • #mysql 8.0 踩坑日记
  • #我与Java虚拟机的故事#连载04:一本让自己没面子的书
  • #我与Java虚拟机的故事#连载16:打开Java世界大门的钥匙
  • (11)MSP430F5529 定时器B
  • (AtCoder Beginner Contest 340) -- F - S = 1 -- 题解
  • (LeetCode C++)盛最多水的容器
  • (补充):java各种进制、原码、反码、补码和文本、图像、音频在计算机中的存储方式
  • (二)pulsar安装在独立的docker中,python测试
  • (回溯) LeetCode 40. 组合总和II
  • (九)信息融合方式简介
  • (生成器)yield与(迭代器)generator
  • (数位dp) 算法竞赛入门到进阶 书本题集
  • (一)WLAN定义和基本架构转
  • (已解决)报错:Could not load the Qt platform plugin “xcb“
  • ***详解账号泄露:全球约1亿用户已泄露
  • .bat批处理(十):从路径字符串中截取盘符、文件名、后缀名等信息
  • .NET 8.0 中有哪些新的变化?
  • .NET CF命令行调试器MDbg入门(一)
  • .NET导入Excel数据
  • .Net高阶异常处理第二篇~~ dump进阶之MiniDumpWriter