ARouter源码探究
1. 疑问
- 如何做到支持直接解析标准URL进行跳转,并自动注入参数到目标页面中?
- 如何做到支持Multidex、InstantRun?
- 如何做到映射关系按组分类、多级管理,按需初始化?
- 依赖注入是如何实现的?
- 如何做到支持添加多个拦截器,自定义拦截顺序?
- 页面、拦截器、服务等组件如何自动注册到框架?
- 如何做到支持多模块工程使用?
- 如何做到支持获取Fragment?
源码是最好的老师,我们带着这些疑问去源码中寻找答案;
其实ARouter有上述功能我总结有二个关键点:APT、反射
下面我们从编译期,ARouter初始化,route三个方面进行分析
2. 编译期间做了什么?
https://github.com/alibaba/ARouter.git
我们以官方源码为例进行剖析,下载源码后终端输入如下命令
cd ARouter && ./gradlew clean assembleDebug
构建成功后你看到的目录结构如下
App:主工程
arouter-annotation:存放ARouter注解的Module
arouter-compiler:编译期间动态生成代码的逻辑
arouter-api: ARouter大部分的内部实现设计
arouter-register:支持第三方加固
module-java:跨模块调用演示
module-kotlin:支持Kotlin
我们先熟悉下几个类
Route注解
可以看到Route注解中的path就是跳转的路由,group是其对应的分组(按需加载用到);需要说明的是ARouter除了支持跳转Activity外还支持其他类型
Arouter的跳转类型
从上面可推测目前应该支持挑转Activity,Service,ContentProvider以及Java接口
APT(注解处理器)
APT(Annotation Processing Tool)即注解处理器,是一种处理注解的工具,确切的说它是javac的一个工具,它用来在编译时扫描和处理注解。注解处理器以Java代码(或者编译过的字节码)作为输入,生成.java文件作为输出。简单来说就是在编译期,通过注解生成.java文件。用过Dagger、EventBus等注解框架同学都能感受到,使用这些框架只要写一些注解就可以了,使用起来非常方便;其实它们都是通过读取注解,生成了一些代码而已;
Arouter的参数的自动注入,支持拦截,path和类如何建立映射,都是和注解处理器分不开的;
我们来看app工程中自动生成的代码有哪些
我挑选分组为test、service代码来看看,可以看到下面二个类名的末尾都是使用自身的组名来拼接而成的;因为ARouter是运行期间按组加载路由而不是一次性载入,而下面的二个类也正是基于这一考虑而生成的;每个类只负责加载自己组内路由,一个类只做一件事情
既然ARouter要做到按组加载路由,那肯定有一个存放所有分组映射的类,以便于快速定位分组类,我们发现有个Arouter$$Root$app类,它就是我们猜想的那个类,key存放组名,value存放其对应的映射类
拦截器生成类:
可以看到该类中包含了声明的所有拦截器类,key对应就是其优先级
我们用一张图概括编译期间做了啥
从上图看出编译器做了二件事情
- APT扫描指定注解的java类文件
- 通过扫描分析生成对应的java类
3. 初始化做了什么?
一张图总结如下:
下面我们具体分析下
//ARouter.java
/**
* 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.");
hasInit = _ARouter.init(application);
if (hasInit) {
_ARouter.afterInit();
}
_ARouter.logger.info(Consts.TAG, "ARouter init over.");
}
}
//_ARouter.java
protected static synchronized boolean init(Application application) {
mContext = application;
LogisticsCenter.init(mContext, executor);
logger.info(Consts.TAG, "ARouter init success!");
hasInit = true;
// It's not a good idea.
// if (Build.VERSION.SDK_INT > Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
// application.registerActivityLifecycleCallbacks(new AutowiredLifecycleCallback());
// }
return true;
}
我们看到Arouter的初始化其实最后还是由LogisticsCenter这个类来做的
我们直接从第一个else语句后面看源代码如下
//LogisticsCenter.java
//public static final String ROUTE_ROOT_PAKCAGE = "com.alibaba.android.arouter.routes";
//public static final String DOT = ".";
//public static final String SDK_NAME = "ARouter";
if (ARouter.debuggable() || PackageUtils.isNewVersion(context)) {
logger.info(TAG, "Run with debug mode or new install, rebuild router map.");
// These class was generated by arouter-compiler.
//1、读取com.alibaba.android.arouter.routes包名下所有类
routerMap = ClassUtils.getFileNameByPackageName(mContext, ROUTE_ROOT_PAKCAGE);
if (!routerMap.isEmpty()) {
//2、把指定包名下的类都保存到本地了,便于下次直接从文件读取
context.getSharedPreferences(AROUTER_SP_CACHE_KEY, Context.MODE_PRIVATE)
.edit()
.putStringSet(AROUTER_SP_KEY_MAP, routerMap)
.apply();
}
// Save new version name when router map update finishes.
PackageUtils.updateVersion(context);
} else {
logger.info(TAG, "Load router map from cache.");
//3、从sp中直接读取类信息
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();
//4、将指定前缀的包名的类加载到内存中(Warehouse)
for (String className : routerMap) {
if (className.startsWith("com.alibaba.android.arouter.routes.ARouter$$Root")) {
// This one of root elements, load root.
((IRouteRoot) (Class.forName(className).getConstructor().newInstance()))
.loadInto(Warehouse.groupsIndex);
} else if (className.startsWith("com.alibaba.android.arouter.routes.ARouter$$Interceptors")) {
// Load interceptorMeta
((IInterceptorGroup) (Class.forName(className).getConstructor().newInstance()))
.loadInto(Warehouse.interceptorsIndex);
} else if (className.startsWith("com.alibaba.android.arouter.routes.ARouter$$Providers")) {
// Load providerIndex
((IProviderGroup) (Class.forName(className).getConstructor().newInstance()))
.loadInto(Warehouse.providersIndex);
}
}
小结上面代码,主要做了二件事情,
寻找指定包名的所有类
通过线程池读取所有dex中指定包名类,此处也就解释了为什么ARouter支持InstantRun、MultiDex
通过反射创建对象,并加载到内存(Warehouse)
可以看到初始化过程其实加载到Warehouse类中有groupsIndex、interceptorsIndex、providersIndex