现在最流行的路由框架应该是阿里的ARouter,这几乎是组件化应用的必备了。但是ARouter用起来稍微有一点不爽,不爽在以下两点:
- 没有一个规范化的api式的调用方式:项目大了,调用路由的方法分布在项目各处,难以查找;
- 对startActivityForResult支持不够友好:按照传统方式,在onActivityResult中处理,比较分散。
基于以上问题,闲来无事,手撸一个自己的路由框架IRouter,基本使用方式如下:
1 | interface IRouterService { |
具体的配置方式请参考IRouter,本文主要是解析源码。
如此调用方式,很像是Retrofit的方式,打开一个activity就像请求一个api一样。从这里可以体现出解决了上述的两个痛点:
- 类似API的调用方式,集中管理路由路径;
- startActivityForResult中添加回调,哪里调用,就在哪里处理结果,结构紧凑。
下面进入源码解析。
与ARouter源码分析这篇文章一样,我们分析时候要按照时态去分析这个框架在运行时和编译时做的事情。
运行时
其实从上述调用的方式,有过热门开源框架源码阅读经验的,都能猜出个大概。
先从IRouter这个类创建IRouterService实例说起。使用过Retrofit的同学都知道,创建一个接口类,通过注解标注方法,不用提供具体的实现流程,就能完成网络请求。其实这并不难,这是通过动态代理实现的。我们来看IRouter.create的代码:
1 | public <T> T create(Class<T> tClass) { |
通过动态代理,我们创建了一个IRouterService的实现类,这个类在调用相关方法的时候,比如topicDetail这个方法,都会经由InvocationHandler的invoke方法来代理完成。
接下来,从invoke中调用了parseMethod方法,这个方法比较简单,主要是用于解析方法注解和参数注解,取出其中的值,比如跳转路径path和参数的key - value键值对,通过path查询出相对应的Activity的class,这些值最终汇总起来,返回一个Navigator对象,这个就是真正要执行跳转的地方。
上面提到通过path查询出Activity对应的class,既然要查询,肯定要事先存储后才能被查询到。这就涉及到编译时做的工作了。
编译时
一、APT部分
其实读过其他一些开源框架的人对这部分一定不会陌生。
这部分工作与ARouter类似,就是通过**@RoutePath**注解标记目标Activity,然后再通过注解处理器来获取到path - activity.class的对应关系,将这个对应关系,生成成一个类,我们查看一个生成的类的示例如下:
1 | package com.github.boybeak.irouter.loader; |
这些所有生成的类在一个包名com.github.boybeak.irouter.loader
底下,这很重要,因为我们要在接下来的过程,通过这个包名去筛选生成的loader类。
理解这部分,需要对APT(注解处理器)和java poet比较了解。
二、ASM部分
不太了解ASM的,可以通过这篇文章ASM库介绍与使用来了解,简单来说,ASM就是一款修改class文件的工具。能用来动态生成class文件,也可以修改已经存在的class文件。
有这样的利器,我们能做的事就太多了。
这部分,其实我就是参考了ARouter的做法,改成了自己的一些逻辑。
接下来我们要编写的是一个gradle plugin,我们主要是利用其中的Transform工具,官方解释在这里,其作用就是在编译时,会挨个遍历我们的源码、类库、jar包等。附上一个教程Gradle 学习之 Android 插件的 Transform API。
Path - activity.class的对应关系是通过LoaderManager来查询的,我们看一下这个类的代码:
1 | public final class LoaderManager { |
我们需要注意其中的一个方法——load
,我们看到,这个类在构建方法里调用了init方法,init里又调用了load方法,但是这个load方法却是留白的。这样调用有什么用呢?
其实这个留白方法是我们为ASM留的一个修改的入口。
1 | apply plugin: 'i-router-register' |
在app.gradle中,使用这个插件,我们的transform就能顺利运行起来发挥作用了。
我们需要查看一下RegisterTransform的代码了:
1 | public class RegisterTransform extends Transform { |
这里使用了Scanner来扫描TransformInvocation类,Scanner是我们自定义的类,主要作用就是通过包名和类名,查找我们的loader类和LoaderManager所在的jar包。我们查看其scan方法:
1 | for (TransformInput input : transformInvocation.getInputs()) { |
通过回调,我们将查找到的loader类和LoaderManager所在jar返回给transform,并交由我们的asm工具来处理。
1 | public class ASM { |
主要的修改逻辑就在HackMethodVisitor这个类中,注意其中的visitInsn方法,这就是ASM真正发挥作用的地方,这里的逻辑就是为loadInto方法添加加载loader的代码。
以demo中的v2ex为例,修改后的代码load代码如下:
最后一点细节
到目前为止,主要的流程已经结束了,接下来是一些细节部分。
- LoaderManager在加载loader的时候,会针对path做归并,比如同为app/main和app/user被归并为一组,这样在使用的时候,可以按组做实际载入。
- 用于跳转的Navigator是有缓存的,用来减少查询次数,因为path - activity.class的对应关系并不是动态变化的,如果缓存中有已经用过的,则清空其intent的参数部分重复利用即可。
- 对于startActivityForResult的集中调用,可以参考我的另外一个开源项目——Starter中的SAFR项目。这里在FragmentActivity中,通过一个fragment去代理了startActivityForResult的过程,从而拦截了回调结果;类似的,在非FragmentActivity中,通过了一个全透明的代理了此过程,这里学习了Glide通过一个fragment来探测生命周期的方式。
总结
通过这样一个自己动手的过程,我们熟悉了的编写APT和gradle plugin的过程,学会了ASM的基本用法。
在这个项目中,学习了很多其他优秀开源项目的经验,比如:
- 主流程是参考了ARouter,但是去掉了应用启动时候,从codeDir找到apk来解析路由路径的过程;
- 集中的api式调用,参考了Retrofit的动态代理;
- 通过Fragment拦截startActivityForResult的结果,参考了Glide向宿主activity添加无UI的Fragment的方式。
因此,多读源码可以开拓思维,当自己想开发自己的工具框架时候,就可以信手拈来。
Author: boybeak