源码解读Arouter是如何实现的
Posted 丶笑看退场
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了源码解读Arouter是如何实现的相关的知识,希望对你有一定的参考价值。
原理
我们在代码里加入的@Route注解,会在编译时期通过apt生成一些存储path和activityClass映射关系的类文件,然后app进程启动的时候会拿到这些类文件,把保存这些映射关系的数据读到内存里(保存在map里),然后在进行路由跳转的时候,通过build()方法传入要到达页面的路由地址,ARouter会通过它自己存储的路由表找到路由地址对应的Activity.class(activity.class = map.get(path)),然后new Intent(),当调用ARouter的withString()方法它的内部会调用intent.putExtra(String name, String value),调用navigation()方法,它的内部会调用startActivity(intent)进行跳转,这样便可以实现两个相互没有依赖的module顺利的启动对方的Activity了。
源码
### _ARouter init()
public synchronized static void init(Context context, ThreadPoolExecutor tpe) throws HandlerException {
.....
try {
long startInit = System.currentTimeMillis();
//billy.qi modified at 2017-12-06
//load by plugin first
loadRouterMap();
if (registerByPlugin) {
logger.info(TAG, "Load router map by arouter-auto-register plugin.");
} else {
Set<String> routerMap;
// It will rebuild router map every times when debuggable.
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.
routerMap = ClassUtils.getFileNameByPackageName(mContext, ROUTE_ROOT_PAKCAGE);
if (!routerMap.isEmpty()) {
context.getSharedPreferences(AROUTER_SP_CACHE_KEY, Context.MODE_PRIVATE).edit().putStringSet(AROUTER_SP_KEY_MAP, routerMap).apply();
}
PackageUtils.updateVersion(context); // Save new version name when router map update finishes.
} else {
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();
for (String className : routerMap) {
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)) {
// Load interceptorMeta
((IInterceptorGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.interceptorsIndex);
} else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_PROVIDERS)) {
// Load providerIndex
((IProviderGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.providersIndex);
}
}
}
}
- 初始化操作,内部调用
LogisiticsCenter init
帮我们管理逻辑 - 第一次加载,
ClassUtils.getFileNameByPackageName(mContext, ROUTE_ROOT_PAKCAGE);
先通过com.alibaba.android.arouter.routes
包名,扫描下面包含的所有ClassName,后面对其进行存储Sp。第二次加载就直接从Sp中获取。 - 然后遍历、匹配,满足条件的添加到具体的集合中,按照文件的前缀不同,将他们添加到映射表中Warehouse的
groupsIndex
、interceptorsIndex
、providersIndex
中
### _ARouter.getInstance().build(path)
protected Postcard build(String path) {
if (TextUtils.isEmpty(path)) {
throw new HandlerException(Consts.TAG + "Parameter is invalid!");
} else {
PathReplaceService pService = ARouter.getInstance().navigation(PathReplaceService.class);
if (null != pService) {
path = pService.forString(path);
}
return build(path, extractGroup(path));
}
}
根据PathReplaceService
获得预处理路径,这个接口是Iprovider
的子类
extractGroup(path)
这个方法,这个方法主要是获取分组名称。切割path字符串,默认为path中第一部分为组名。这就证明了如果我们不自定义分组,默认就是第一个分号的内容。
build方法,最终返回的是一个Postcard对象。
_ARoter navigation(Class<? extends T> service)
会调用LogisticsCenter.completion(postcard)
。
- 首先,根据path在Warehouse.routes映射表中查找对应的RouteMeta。但是,第一次加载的时候,是没有数据的。(而第一次加载是在LogisticsCenter.init()中,上面也说了。因此只有
Warehouse
的groupsIndex
、interceptorsIndex
、providersIndex
有数据),因此这个时候routeMeta=null。所以,这个时候会先从Warehouse.groupsIndex中取出类名前缀为com.alibaba.android.arouter.routes.ARouter$$Group$$group
的文件 - 接着,将我们添加@Route注解的类映射到Warehouse.routes中;
- 然后将已经加载过的组从
Warehouse.groupsIndex
中移除,这样也避免了重复添加进Warehouse.routes;注意,这个时候Warehouse.routes
已经有值了,所以重新调用本方法(completion(postcard))
执行了else代码块。
完成了对Warehouse.providers、Warehouse.routes的赋值。
那么Warehouse.interceptors又是在哪里赋值的呢?
### LogisticsCenre.java
provider = providerMeta.getConstructor().newInstance();
provider.init(mContext);
Warehouse.providers.put(providerMeta, provider);
instance = provider;
### InterceptorServiceImpl.java
public void init(final Context context) {
.....
IInterceptor iInterceptor = interceptorClass.getConstructor().newInstance();
iInterceptor.init(context);
Warehouse.interceptors.add(iInterceptor);
}
ARouter缺陷
ARouter
的缺陷就在于拿到这个Map
的过程,我们在使用ARouter
时都需要初始化,ARouter
所做的即是在初始化时利用反射扫描指定包名下面的所有className
,然后再添加map
中
//源码代码,插桩前
private static void loadRouterMap() {
//registerByPlugin一直被置为false
registerByPlugin = false;
}
//插桩后反编译代码
private static void loadRouterMap() {
registerByPlugin = false;
register("com.alibaba.android.arouter.routes.ARouter$$Root$$modulejava");
register("com.alibaba.android.arouter.routes.ARouter$$Root$$modulekotlin");
register("com.alibaba.android.arouter.routes.ARouter$$Root$$arouterapi");
register("com.alibaba.android.arouter.routes.ARouter$$Interceptors$$modulejava");
register("com.alibaba.android.arouter.routes.ARouter$$Providers$$modulejava");
register("com.alibaba.android.arouter.routes.ARouter$$Providers$$modulekotlin");
register("com.alibaba.android.arouter.routes.ARouter$$Providers$$arouterapi");
}
默认通过扫描dex
的方式进行加载,通过gradle
插件进行自动注册可以缩短初始化时间,同时解决应用加固导致无法直接访问dex
文件,初始化失败的问题
以上是关于源码解读Arouter是如何实现的的主要内容,如果未能解决你的问题,请参考以下文章