ARouter源码分析
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ARouter源码分析相关的知识,希望对你有一定的参考价值。
参考技术A Annotation Processing Tool,自定义注解处理器。
搞android的基本上都知道这个吧。许多第三方库都使用了APT去实现自己的功能,比如butterknife,比如X2C,比如我们要讲的ARouter。
其基本做法是:
一般是自定义gradle Transform + ASM,实现AOP,可以在编译期修改project和第三方依赖库中的class文件(比如ARouter源码中的arouter-gradle-plugin模块),与APT主要是生成.java文件不同,ASM操作的是.class文件。
自定义gradle Transform功能很强大,可以与ASM结合,修改.class,也可以操作资源文件(比如统一压缩图片,转png大图为webp等)。
至于ASM,基于修改.class文件,我们即可以用ASM来插桩统计方法耗时,也可以用来实现自动化埋点,甚至是修改第三方lib中的crash...
使用方法可以看 ARouter 。
带着问题看源码,这里主要的问题是:
ARouter的核心方法。
这个方法算是核心中的核心了。
其实也只做了两件事情
总的来说,只干了一件事情,扫描所有的dex文件,找到com.alibaba.android.arouter.routes包下的所有文件并返回。这里的操作都是耗时操作。
但是com.alibaba.android.arouter.routes包下都是什么文件呢?
比如:
比如:
比如:
比如:
这些都是APT生成的辅助类。
①项目编译期,通过APT,生成辅助类(所有的辅助类包名都是com.alibaba.android.arouter.routes)
包括
②ARouter#init初始化的时候,扫描dex文件,找到①生成的辅助类文件(也即是包com.alibaba.android.arouter.routes下的文件),放到routerMap中
注意,这里没有实例化IRouteGroup,IRouteGroup的信息都在IRouteRoot中,这样做的目的是为了实现分组route的加载,用到了哪个group的route的信息,才会加载这个group的信息,没用到就不加载。这里可以仔细想想IProviderGroup/IInterceptorGroup/IRouteGroup的区别。
该方法执行完了以后,
至此,就完成了初始化路由表的操作。
我们回过头来瞄一眼ARouter#init,里面初始化路由表以后,执行了_ARouter#afterInit
这一句看着很熟悉。
页面路由跳转/IProvider/拦截器都是ARouter.getInstance().build("/test/activity").navigation()这种形式的话,我们就先从拦截器interceptorService = (InterceptorService) ARouter.getInstance().build("/arouter/service/interceptor").navigation();开始分析吧。
虽然这里我们是想看拦截器的实现,但是要明确一点:InterceptorServiceImpl是IProvider的实现类,获取InterceptorService也就是获取一个IProvider。有一点绕,简单来说,ARouter使用一个IProvider来实现拦截器的初始化。
后面的逻辑就变成了获取一个IProvider上了。
也即是ARouter.getInstance().build("/arouter/service/interceptor").navigation()方法中,ARouter.getInstance().build("/arouter/service/interceptor")做的事情就是创建一个Postcard,其path是"/arouter/service/interceptor",group是"arouter".
简单来说,这里做的事情有
注意addRouteGroupDynamic(postcard.getGroup(), null)这个方法,通过groupName去groupIndex中查找,那"arouter"对应的是谁呢?正是ARouter$$Group$$arouter.class。
反射创建ARouter$$Group$$arouter对象,并执行ARouter$$Group$$arouter#loadInto方法
现在我们回过头来继续看LogisticsCenter#completion
总结一下LogisticsCenter#completion方法做了啥:
以ARouter.getInstance().build("/arouter/service/interceptor").navigation()举例说明就是:
init方法做的事情很单一,就是一次性实例化全部的拦截器,存到 Warehouse.interceptors中。(想想为什么要这么做?)
这样,ARouter.getInstance().build("/arouter/service/interceptor").navigation()就分析完了,ARouter#init的时候,会创建所有的拦截器实例。ARouter.getInstance().build("/arouter/service/interceptor").navigation()方法返回的是InterceptorServiceImpl的实例。
另外,_ARouter#navigation(Context, Postcard, int, NavigationCallback)方法的最后,调用了_ARouter#_navigation
方法看着长,内容却很简单:
这样, ARouter#init 就分析完了, 总结 一下:
另外使用ARouter.getInstance().build("path").navigation()方法 获取IProvider的流程 如下:
Activity跳转的流程如下:
至此,我们回答了问题1/问题2和问题4.
下面我们来看下剩下的问题
问题3:拦截器是如何生效的?
我们可以看看_ARouter
执行_ARouter#navigation的时候,执行了interceptorService.doInterceptions方法,前面我们已经知道,执行了interceptorService实际上是InterceptorServiceImpl。
这里一个一个调用拦截器,如果有拦截器拦截,就中断调用,否则,调用下一个拦截器进行拦截。
所以, 拦截器 总结如下
最后,我们来看下最后一个问题。
问题5 ARouter的Gradle Plugin做了哪些优化?
该问题的关键是LogisticsCenter#loadRouterMap
上面的是gradle plugin修改之前的,下面的loadRouterMap是gradle plugin修改之后的。
瞄一眼register方法我们就能明白,这还是之前的那一套,跟不使用gradle plugin不同的地方在于,这里不需要扫描dex去找IRouteRoot/IInterceptorGroup/IProviderGroup,在编译期,gradle plugin就已经找到了这些,然后生成新的loadRouterMap方法。
ARouter源码分析—— 缓存与优化
Arouter
源码分析系列建议从最初开始阅读,全部文章请访问https://github.com/AlexMahao/ARouter
本篇博客意在记录ARouter
中的一些优秀策略。
辅助类加载机制
ARouter
在实现基本功能时,使用apt
在指定包名下生成了一些辅助类。辅助类的查询逻辑如下。
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.
// 获取com.alibaba.android.arouter.routes的类列表,及Processor生成的辅助类
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>()));
判断是否是debug
或者是否是新版本,其中一个成立,则重新查询并且遍历一下指定包下的所有辅助类。并且将辅助类的全路径保存到sp
中。
如果不满足条件,则从sp
中取。
尽可能减少耗时操作调用的次数。
路由的懒加载
对于一个大型app,存在的路由地址数量很大。而ARouter
的初始化方法都是在Application
中,那么势必导致app
加载时间过长。ARouter
通过分组懒加载的形式进行加载。
ARouter
在初始化的时候,只是加载了分组的类,即路由清单的class
,而没有加载详细的路由清单。
public class ARouter$$Root$$app implements IRouteRoot
@Override
public void loadInto(Map<String, Class<? extends IRouteGroup>> routes)
routes.put("test", ARouter$$Group$$test.class);
routes.put("yourservicegroupname", ARouter$$Group$$yourservicegroupname.class);
然后当有路由地址跳转的时候,判断是否能查寻对应地址信息,如果没有,则会根据路由地址确定其分组,然后加载该组的路由清单。
public synchronized static void completion(Postcard postcard)
if (null == postcard)
throw new NoRouteFoundException(TAG + "No postcard!");
// 获取路由地址对应的信息
RouteMeta routeMeta = Warehouse.routes.get(postcard.getPath());
// 如果为null可能是由于对应组别的路由清单没有加载
if (null == routeMeta) // Maybe its does't exist, or didn't load.
// 查询对应组的路由清单
Class<? extends IRouteGroup> groupMeta = Warehouse.groupsIndex.get(postcard.getGroup()); // Load route meta.
if (null == groupMeta)
// 如果不存在,则说明当前路径不存在
throw new NoRouteFoundException(TAG + "There is no route match the path [" + postcard.getPath() + "], in group [" + postcard.getGroup() + "]");
else
// Load route and cache it into memory, then delete from metas.
try
if (ARouter.debuggable())
logger.debug(TAG, String.format(Locale.getDefault(), "The group [%s] starts loading, trigger by [%s]", postcard.getGroup(), postcard.getPath()));
// 加载分组的路由清单
IRouteGroup iGroupInstance = groupMeta.getConstructor().newInstance();
iGroupInstance.loadInto(Warehouse.routes);
// 已加载的路由清单,将其从根节点移除
Warehouse.groupsIndex.remove(postcard.getGroup());
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(postcard); // Reload
以上是关于ARouter源码分析的主要内容,如果未能解决你的问题,请参考以下文章