在阅读源码前,请先下载源码:ARouter
最近阅读ARouter源码,发现这真的是一个非常优秀的框架。激发出兴趣来读一下他的源码,实际上,这个框架的结构非常简单。这个框架可以分为主流程和辅助流程来拆开分析。
主流程包含编译时和运行时两个部分,其中编译时主要做的是路由路径表的构建,运行时主要做的是路由路径表的加载;
辅助流程主要就是做启动优化。
主流程
1. 编译时
这部分主要涉及到的是路由路径表的构建,其实现原理是APT,即注解处理器。
使用ARouter时候,需要在目标类上,通过**@Route注解进行标记,注解处理器处理的就是这个注解。打开源码路径下的arouter-compiler这个module,找到RouteProcessor,这个类就是用来处理@Route**注解的类。这里需要了解的知识,除了APT,还有java-poet,请自行了解这些。
Processor类的入口方法是process方法,这个方法返回true,则这个处理器已经完成了自己的任务,不会被重复调用。其他比较重要的方法有getSupportedSourceVersion,getSupportedAnnotationTypes等。
1 | Set<? extends Element> routeElements = roundEnv.getElementsAnnotatedWith(Route.class); |
通过这个方法,获取所有被**@Route**标记的元素。
获取到routeElements后,在parseRoutes方法进行处理。我们以最常用的Activity为例,进行分析。
1 | rootMap.clear(); //用来分类存储标记元素 |
最后将分类号的元素信息,存储在成员变量groupMap中去。
1 | private Map<String, Set<RouteMeta>> groupMap = new HashMap<>(); |
然后再通过这个groupMap,借助java-poet,来生成真实的类。如下:
1 | public class ARouter$$Group$$test implements IRouteGroup { |
2. 运行时
这部分主要做的是,在*ARouter.init()*时候,将上过程生成的路径表加载到内存中。
如果你以官方demo程序验证这一步,需要将app/build.gradle中的
apply plugin: 'com.alibaba.arouter'
这一行代码注释掉。
我们以ARouter.init方法为入口,实际上最终实现init流程的是LogisticsCenter类的init方法。
1 | public synchronized static void init(Context context, ThreadPoolExecutor tpe) throws HandlerException { |
这里需要着重看的是这一句:
1 | routerMap = ClassUtils.getFileNameByPackageName(mContext, ROUTE_ROOT_PAKCAGE); |
我们查看这个方法:
1 | public static Set<String> getFileNameByPackageName(Context context, final String packageName) throws PackageManager.NameNotFoundException, IOException, InterruptedException { |
注意其中的getSourcePaths方法,这是从代码目录,来获取所有代码目录,然后在
getFileNameByPackageName找出以com.alibaba.android.arouter.routes
为开头包名的类,这些就是我们在步骤1中生成的辅助类。
这个方法结束后,回到LogisticsCenter#init方法,接下来要做的就是,把加载到的辅助类,通过反射生成对象,再调用其loadTo方法,将路由路径表加载到Warehouse类中去,方便以后的查询。
辅助流程
在以上的流程中,有一个严重的问题,那就是执行ARouter#init方法的时间过长,以源码的demo为例,在InstantRun的情况下,OnePlus5T需要100多毫秒才能初始化完,这对于程序启动优化来说,是一个不可忽视的时间了。那么如何解决这个问题呢?
1 | MainActivity: init cost 134 |
这就是在上一步中,要求你注释掉的代码起作用了,将apply plugin: 'com.alibaba.arouter'
解除注释,让其发挥作用。
这里需要关注的module是arouter-gradle-plugin
。
先来看一下,使用了apply plugin: 'com.alibaba.arouter'
的神奇效果。
1 | MainActivity: init cost 19 |
通过优化,让ARouter#init消耗时间直接降低了一个数量级,那么arouter-gradle-plugin
是怎么做到的呢?
这需要你先了解一下[ASM](/android/ASM.md)。简单来说,这是一种字节码编程技术,通过修改编译后的字节码的方式,来对原始逻辑增强。
我们再来看ARouter#init的最终实现类和方法LogisticsCenter#init:
1 | public synchronized static void init(Context context, ThreadPoolExecutor tpe) throws HandlerException { |
我们可以看到,当registerByPlugin
为true的时候,则只是打印了一句日志,我们再看loadRouterMap()这个方法:
1 | private static void loadRouterMap() { |
这里的逻辑非常简单,到底在哪里去加载的路由路径表呢?我们看这个方法的注释,发现,这个方法是被arouter-auto-register
自动生成的。
我们打开arouter-gradle-plugin/resources/META-INF/gradle-plugins
这个目录,可以看到,有一个com.alibaba.arouter.properties文件,查看其内容:
1 | implementation-class=com.alibaba.android.arouter.register.launch.PluginLaunch |
这个PluginLaunch便是此gradle plugin的入口类。查看此类:
1 | public class PluginLaunch implements Plugin<Project> { |
我们查看其代码,可以发现,这里一共做了三件事:
- 判断是否为app module,如果不是,则不做任何事,在app module下做2和3两步;
- 生成了一个RegisterTransform,并为其静态变量registerList赋值,注意此处赋值的registerList中包含的三个对象;
- 注册此RegisterTransform。
接下来,就轮到RegisterTransform来执行了。
Transform类,简单来说,就是可以在编译时,扫描所有的jar和class,包括引用类库中的。在扫描过程中,就可以借助ASM技术对目标类进行更改。
我们看其入口方法transform:
1 |
|
省去了一些细节,只保留了主线逻辑,我们可以看到,其扫描到的jar和class都经过了ScanUtils的方法来处理,我们继续跟踪下去,会发现,scanJar也是循环调用的scanClass,这样我们直接看scanClass方法:
1 | static void scanClass(InputStream inputStream) { |
这里就又涉及到了ASM的知识,ScanClassVisitor是访问某个类的内部结构。
version:类的版本;
access:表示类的访问权限,public,private,protected等;
name:类的名字;
signature:有无泛型;
superName:其父类;
interfaces:其实现的接口;
在ScanClassVisitor中,并没有对类做修改,只是从遍历过的类中,把我们关心的类挑出来。那么,我们关心哪些类呢?
在PluginLaunch类中,我们注册了三个ScanSettings类,分别是IRouteRoot、IInterceptorGroup和IProviderGroup,也就是说,我们把实现了这三个接口的类,挑出来,加入到各自对应的ScanSettings类中记录起来。这三个接口是不是很熟悉?就是通过APT生成的用来记录路由路径表的类。
等收集好了这些记录的路径表信息后,就可以对LogisticsCenter通过ASM进行修改了。我们接着看RegisterTransform#transform方法中剩下的逻辑。
1 | if (fileContainsInitClass) { |
注意此处的insertInitCodeTo方法,这就是ASM修改的入口了。这里不对修改过程进行详细解释了。我们直接对比看LogisticsCenter修改前后关键代码的对比。
在app/build目录下,找到生成的apk文件,通过AndroidStudio来查看其中的class,找到关键LogisticsCenter关键方法loadRouterMap。
具体过程如下:
app/build/outputs/apk/debug/app-debug.apk -> classes.dex(双击) -> 找到LogisticsCenter#loadRouterMap方法 -> 右键: show Bytecode。
1 | // 不使用apply plugin: 'com.alibaba.arouter' |
1 | // 使用apply plugin: 'com.alibaba.arouter' |
对比发现,使用apply plugin: 'com.alibaba.arouter'
后,这个方法增加了很多代码,基本上就是在加载路由路径表。使用这个gradle插件的基本思想就是,将查找路由路径表的过程,从运行时提前到了编译时,这算是一种AOT(Ahead of time)思想。
将最耗时的查找过程提前,也就解决了ARouter初始化时间过长的问题。
Author: boybeak