ARouter的使用及其原理
ARouter的使用及其原理
在阅读这篇文章之前,如果对APT注解处理器和poet框架不了解的,可以参考以下两篇博客:
ARouter介绍
- ARouter是一个用于帮助 Android App 进行组件化改造的框架 —— 支持模块间的路由、通信、解耦。它可以实现组件间的路由功能。路由是指从一个接口上收到数据包,根据数据路由包的目的地址进行定向并转发到另一个接口的过程。这里可以体现出路由跳转的特点,非常适合组件化解耦。
ARouter的简单使用
现在我们有两个module,一个是app主工程,一个是login组件,从app组件跳转到login组件需要使用ARouter。
在使用ARouter之前我们要进行ARouter和注解依赖的注入,以及ARouter在application’中的初始化,然后才可以正式开始使用,比如我们要在刚才说的app组件通过点击按钮跳转到login组件我们可以这么写:
-
在login组件对应的Activity上面添加注解@Route(path = “/login/login1”)
@Route(path = "/login/login1") public class Login extends AppCompatActivity { //...... } }
-
在app组件中,点击事件进行如下书写:
login.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { ARouter.getInstance().build("/login/login1").navigation(); } });
只需要这样操作,两个模块就不用相互有任何直接的依赖,直接就可以进行跳转,模块与模块之间就相互独立了。
至此,ARouter的使用,就是分为三步:
- 添加依赖初始化
- 为跳转的目标Activity设置注解,参数设置为路径
- 在准备跳转的Activity中,调用ARouter.getInstance().build(“xxxxxx”).navigation()方法即可
说完了用法,我们就来说一下ARouter的源码处理。
ARouter的跳转原理
注解&APT
在使用过程中,我们在目标Activity上面添加了@Router()注解,我们看一下这个注解的定义:
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.CLASS)
public @interface Route{
String path();
……
}
Route里面有一个主要的参数path,这个就是表示了Activity的路由地址。
而APT就是注解处理器,它的作用是在编译阶段扫描并处理代码中的注解,然后根据注解输出Java文件。
自己构造APT,需要写一个类继承AbstractProcessor然后重写它的方法,比如init()…
注解处理器RouteProcessor
ARouter的注解处理器是RouteProcessor
@AutoService(Processor.class) //这个库为ARouter的注解处理器APT实现了自动注册
@SupportedAnnotationTypes({ANNOTATION_TYPE_ROUTE, ANNOTATION_TYPE_AUTOWIRED}) //表明了当前APT是处理那些注释的
public class RouteProcessor extends BaseProcessor {
super.init(processingEnv);
......
}
这里面调用了其父类BaseProcessor的init方法
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
......
// Attempt to get user configuration [moduleName]
Map<String, String> options = processingEnv.getOptions();
if (MapUtils.isNotEmpty(options)) {
moduleName = options.get(KEY_MODULE_NAME);
generateDoc = VALUE_ENABLE.equals(options.get(KEY_GENERATE_DOC_NAME));
}
......
}
我们看到BaseProcessor的init方法中获取每个模块的moduleName并存入map集合options。
RouteProcessor中的process方法是对注解方法的地方,在这个方法中会获取所有Route注解的元素:
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
if (CollectionUtils.isNotEmpty(annotations)) {
Set<? extends Element> routeElements = roundEnv.getElementsAnnotatedWith(Route.class);
try {
this.parseRoutes(routeElements);
......
}
在第四行中,process方法中获取所有注解了Route的元素放到set集合routeElement中,并把这个当作参数传入this.parseRoutes方法中。这个方法会调用poet库生成Java文件,他生成了两个类,一个是ARouter G r o u p Group Groupxxx implement IRouterGroup,一个是ARouter R o o t Root Rootxxx implement IRouteRoot。下面我们一一来说明:
ARouter Group xxx
看一下例子:
public class ARouter$$Group$$login implements IRouteGroup {
@Override
public void loadInto(Map<String, RouteMeta> atlas) {
atlas.put("/login/login1",
RouteMeta.build(RouteType.ACTIVITY,
Login.class,
"/login/login1",
"login",
null,
-1,
-2147483648));
}
}
由代码可知,这个类实现了IRouterGroup接口,实现了loadInto方法,这个方法中,以路径名path为key,RouteMeta为value放入map集合atlas中。其中RouteMeta是存放了@Route注解修饰的元素的一些重要信息,比如Login.class,有了这个,我们后续可以通过intent来跳转到这个Activity了。这个方法调用之后,activity就完成了注册。
ARouter Root xxx
看一下例子:
public class ARouter$$Root$$login implements IRouteRoot {
@Override
public void loadInto(Map<String, Class<? extends IRouteGroup>> routes) {
routes.put("login", ARouter$$Group$$login.class);
}
}
这个类实现了IRouteRoot接口,它的loadInto方法中,以路径名后面第一个斜杠后面的内容Group为key,以上一个类IRouterGroup类为value放入集合routes中。调用这个方法,可以通过Group来获取到IRouterGroup的实现类(也就是上面介绍的第一个生成的类),通过这个实现类拿到Activity.class。
接下来回过头看一下是如何生成的IRouterGroup类和IRouteRoot类
this.parseRoutes(routeElements)方法
这个方法的参数是所有注解了Route的元素。
这个方法中有这样一段代码:
MethodSpec.Builder loadIntoMethodOfRootBuilder = MethodSpec.methodBuilder(METHOD_LOAD_INTO)
.addAnnotation(Override.class) //添加@Override注释
.addModifiers(PUBLIC) //方法为public
.addParameter(rootParamSpec); //添加一个参数
这个类通过poet框架中的MethodSpec类来实现方法构造,这里构造了loadInto方法,调用3个add为这个方法添加了一些设置,如注释,方法访问权限和参数。构造之后,进入了一层for循环:
for (Element element : routeElements) {
TypeMirror tm = element.asType();
Route route = element.getAnnotation(Route.class);
RouteMeta routeMeta;
// Activity or Fragment
if (types.isSubtype(tm, type_Activity) || types.isSubtype(tm, fragmentTm) || types.isSubtype(tm, fragmentTmV4)) {
Map<String, Integer> paramsType = new HashMap<>();
Map<String, Autowired> injectConfig = new HashMap<>();
injectParamCollector(element, paramsType, injectConfig);
if (types.isSubtype(tm, type_Activity)) {
// Activity
routeMeta = new RouteMeta(route, element, RouteType.ACTIVITY, paramsType);
} else {
// Fragment
routeMeta = new RouteMeta(route, element, RouteType.parse(FRAGMENT), paramsType);
}
routeMeta.setInjectConfig(injectConfig);
}......
categories(routeMeta);
}
然后根据传入参数,所有注释了Route的元素集合routeElement,来创建对应的RouteMeta对象。创建之后调用categories方法对创建的routeMeta进行分组。
categories(routeMeta)方法
private void categories(RouteMeta routeMete) {
if (routeVerify(routeMete)) {
Set<RouteMeta> routeMetas = groupMap.get(routeMete.getGroup());
if (CollectionUtils.isEmpty(routeMetas)) {
Set<RouteMeta> routeMetaSet = new TreeSet<>(new Comparator<RouteMeta>() {
@Override
public int compare(RouteMeta r1, RouteMeta r2) {
try {
return r1.getPath().compareTo(r2.getPath());
} catch (NullPointerException npe) {
logger.error(npe.getMessage());
return 0;
}
}
});
routeMetaSet.add(routeMete);
groupMap.put(routeMete.getGroup(), routeMetaSet);
} else {
routeMetas.add(routeMete);
}
}......
}
categories这个方法会根据每个RouteMeta对象的group来把routeMeta进行分组,拥有相同group的routeMeta被分到一组,存在groupMap中,map结构为<group,set>,也就是说如下图这样的结构:
回到parseRoutes方法
已经对routeMeta进行了分组,然后回到parseRoutes方法中,遍历groupMap,根据不同的key(group),遍历不同的value(set集合),对集合中的routeMeta依次添加loadInto代码实现,生成java代码:
//遍历Map
for (Map.Entry<String, Set<RouteMeta>> entry : groupMap.entrySet()) {
String groupName = entry.getKey();
//构造loadInto方法
MethodSpec.Builder loadIntoMethodOfGroupBuilder = MethodSpec.methodBuilder(METHOD_LOAD_INTO)
.addAnnotation(Override.class)
.addModifiers(PUBLIC)
.addParameter(groupParamSpec);
…………
//获取set
Set<RouteMeta> groupData = entry.getValue();
//遍历Set
for (RouteMeta routeMeta : groupData) {
…………
}
......
//根据刚才构造的loadInto,生成IRouteGroup 的loadInto方法体实现
loadIntoMethodOfGroupBuilder.addStatement(
"atlas.put($S, $T.build($T." + routeMeta.getType() + ", $T.class, $S, $S, " + (StringUtils.isEmpty(mapBody) ? null : ("new java.util.HashMap<String, Integer>(){{" + mapBodyBuilder.toString() + "}}")) + ", " + routeMeta.getPriority() + ", " + routeMeta.getExtra() + "))",
routeMeta.getPath(),
routeMetaCn,
routeTypeCn,
className,
routeMeta.getPath().toLowerCase(),
routeMeta.getGroup().toLowerCase());
…………
}
//生成ARouter$$Group$$login implements IRouteGroup类和Java文件
//NAME_OF_GROUP定义在Consts,为"Arouter$$Group$$",groupFileName为类名
String groupFileName = NAME_OF_GROUP + groupName;
//PACKAGE_OF_GENERATE_FILE为包名,这里通过poet框架的JavaFile类创建类文件,至此代码文件全部生成完成
JavaFile.builder(PACKAGE_OF_GENERATE_FILE,
TypeSpec.classBuilder(groupFileName)
.addJavadoc(WARNING_TIPS)
.addSuperinterface(ClassName.get(type_IRouteGroup))
.addModifiers(PUBLIC)
.addMethod(loadIntoMethodOfGroupBuilder.build())
.build()
).build().writeTo(mFiler);
//分组名和文件名存入Map,以便后续生成IRouteRoot
rootMap.put(groupName, groupFileName);
…………
}
到这里,我们已经生成了ARouter$$Group$$qrcode
的Java文件。同样的逻辑会生成ARouter$$Root$$qrcode
的Java文件。
这两个文件生成完成,我们看一下ARouter是如何应用这两个类的呢?
路由跳转
第一种路由表加载方式
在ARouter的初始化init方法中,经过不断的调用会到达LogisticsCenter.init方法。
LogisticsCenter.init()
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>()));
}
在这个方法中,会进行判断:如果是设置了debug模式或者App版本与记录的不同的情况下,扫描所有Dex文件查找所有com.alibaba.android.arouter.routes开头的文件,然后更新到SharePreferences。否则直接从SharePreferences读缓存,减少解析时间。
解析完成之后,按照这些com.alibaba.android.arouter.routes文件下所有com.alibaba.android.arouter.routes.Arouter R o o t 开 头 的 类 , ∗ ∗ 反 射 出 所 有 A R o u t e r Root开头的类,**反射出所有ARouter Root开头的类,∗∗反射出所有ARouterRoot$$xxx类**,实例化,并调用IRouteRoot实现的loadInto方法把存储group和IRouterGroup的map集合赋值给Warehouse.groupsIndex这个Map中,至此这就获得了所有的group和对应的IRouterGroup。
if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_ROOT)) {
((IRouteRoot(Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.groupsIndex);
}
至此我们解析了IRouteRoot,那么IRouteGroup什么时候解析?
Postcard类
PostCard是RouteMeta的子类,存储了path、group等信息。
当我们调用ARouter.getInstance().build(“xxxxx”).navigation();这句话时,会调用_ARouter的build方法,根据传入的路径(path),获取出path和group创建出postcard。然后navigation方法会直接调用LogisticsCenter.completion(postcard)方法。
在LogisticsCenter.completion(postcard)方法中,会先尝试根据path去取解析过的RouteMeta,但我们目前还没有解析:
RouteMeta routeMeta = Warehouse.routes.get(postcard.getPath());
routeMeta为null,所以我们从刚刚解析得到的Warehouse.groupsIndex集合中通过group去取IRouteGroup类,赋值给iGroupInstance,然后通过这个IRouteGroup类调用loadInto方法,获取到IRouteGroup,进而获取到所有的RouteMeta即拿到了对应的Activity信息,至此我们应用完毕IRouteRoot,从Warehouse.groupsIndex集合中移除它:
Class<? extends IRouteGroup> groupMeta = Warehouse.groupsIndex.get(postcard.getGroup()); // Load route meta.
IRouteGroup iGroupInstance = groupMeta.getConstructor().newInstance();
iGroupInstance.loadInto(Warehouse.routes);
Warehouse.groupsIndex.remove(postcard.getGroup());
由此可以理解出,相同group下的所有IRouteGroup对应的RouteMeta的注册是在其中一个第一次被请求跳转的时候完成的。Warehouse.routes的结构如下:
第二种路由表加载方式
就是使用com.alibaba:arouter-register了,它底下使用的是arouter-auto-register这个库,它能够在编译阶段扫描IRouteRoot的实现类,注册到LogisticsCenter。用这种方式能够避免应用开启后再扫描生成路由表的开销。而且能够避免在某些加壳工具加固后无法获取到dex文件中的com.alibaba.android.arouter.routes包下的内容,导致无法生成注册表的问题。
进行intent跳转
以上的两种路由表加载,通过最初的只包含path和group的简单postcard,获取到routeMeta,然后根据获取到的routeMeta完善postCard的信息(postCard是routeMeta的子类),加载结束后,我们拿到了完整的postCard,通过这个postCard我们就可以通过intent去进行跳转,最后跳转会被安排到主线程:
final Intent intent = new Intent(currentContext, postcard.getDestination());
switch (postcard.getType()) {
case ACTIVITY:
//创建intent
final Intent intent = new Intent(currentContext, postcard.getDestination());
intent.putExtras(postcard.getExtras());
//...
runInMainThread(new Runnable() {
@Override
public void run() {
startActivity(requestCode, currentContext, intent, postcard, callback);
}
});
break;
总结
在我看来,ARouter的使用及原理主要分3个部分。
第一部分:就是代码的书写,在Application中进行ARouter初始化,在要跳转的目标activity上加Route注解。
第二部分:就是在编译阶段,编译阶段时,ARouter的apt注解管理器会获取所有注解Arouter的元素,根据poet框架来构造出loadInto方法,在根据获得的元素,创建出RouteMeta对象,然后根据group来进行分组,然后对每个routeMeta对象生成2个java文件,IRouteRoot和IRouteGroup的实现类,这两个实现类存放了path和activityClass映射关系的类文件,两个map:<group,IRouterGroup>,<path,RouteMeta>。
第三部分又分为两个小部分:
- 初始化,调用的ARouter.init(),有一个类叫Warehouse,这个类里面有几个map,对应我们APT生成的类的map存放数据。这个init通过层层调用,最后到达一个LogisticsCenter.init()方法,在这里会获取到所有配置route的文件,然后通过反射拿到IRouterRoot的实现类,拿到了group和IRouterGroup,将这写入Warehouse的某个map当中。
- 跳转阶段:调用ARouter.getInstance().build("/login/login1").navigation(),这个会调用-Route.build方法,根据传入path,构造出一个postCard(存放了group和path,是RouteMeta的子类),然后通过postCard去获取IRouteGroup类即间接获取RouteMeta,有缓存则直接拿,没有缓存则从Warehouse的map中去拿,拿到后反过来完善postCard的信息,然后调用navigation时,实际上调用的是_ARouter的-navigation方法,这里面自动new一个intent,把postCard中存的activity传入,然后调用runInMainThread回到主线程跳转。(runInMainThread是通过得到主线程的Looper,再判断当前线程是不是主线程,如果不是就通过handler发消息,如果是就直接run。)