ARouter路由框架原理简单分析
前言
ARouter是阿里巴巴推出的一款Android路由框架,官方介绍:一个用于帮助 Android App 进行组件化改造的框架,支持模块间的路由、通信、解耦。
github文档地址:https://github.com/alibaba/ARouter/blob/master/README_CN.md
ARouter支持很多功能,比如可设置拦截器,优先级,可自定义页面分组,添加回调,给上层提供服务等。这些功能本文不做讨论,仅介绍最简单的跳转方式。
一、ARouter解决的问题
一般我们使用ARouter的目的,是为了解决组件间activity跳转的问题。
众所周知,如果从Activity A跳转到B,原始写法可以这么写:
startActivity(new Intent(A.this, B.class));
但是如果AB是位于两个不同组件,且A没有依赖B,则无法通过这种指定目标Activity的方式跳转。当然也可以通过隐式Intent的方式跳转,但是组件多了之后,维护每个页面的隐式Intent规则就比较麻烦了。
那么使用ARouter的话,就可以解决这个问题了。
二、ARouter的简单使用
如何使用ARouter,看官方文档:
1.添加依赖和配置
android { defaultConfig { ... javaCompileOptions { annotationProcessorOptions { arguments = [AROUTER_MODULE_NAME: project.getName()] } } } }
dependencies { // 替换成最新版本, 需要注意的是api // 要与compiler匹配使用,均使用最新版可以保证兼容 compile 'com.alibaba:arouter-api:x.x.x' annotationProcessor 'com.alibaba:arouter-compiler:x.x.x' ... }
2.添加注解
// 在支持路由的页面上添加注解(必选) // 这里的路径需要注意的是至少需要有两级,/xx/xx @Route(path = "/test/activity") public class YourActivity extend Activity { ... }
3.初始化SDK
if (isDebug()) { // 这两行必须写在init之前,否则这些配置在init过程中将无效 ARouter.openLog(); // 打印日志 ARouter.openDebug(); // 开启调试模式(如果在InstantRun模式下运行,必须开启调试模式!线上版本需要关闭,否则有安全风险) } ARouter.init(mApplication); // 尽可能早,推荐在Application中初始化
4.发起路由操作
// 1). 应用内简单的跳转(通过URL跳转在'进阶用法'中) ARouter.getInstance().build("/test/activity").navigation(); // 2). 跳转并携带参数 ARouter.getInstance().build("/test/1") .withLong("key1", 666L) .withString("key3", "888") .withObject("key4", new Test("Jack", "Rose")) .navigation();
简单的描述一下ARouter的用法就是:通过注解的方式给目标Activity设置一个path,然后就可以通过此path跳转到目标Activity。
三、ARouter原理分析:
1.先介绍ARouter源码里几个关键的类:
ARouter.java: 对外提供调用方法的类。
_ARouter.java: ARouter.java的代理类,ARouter.java中的关键方法都在_ARouter.java里实现。
RouteMeta.java: 包含了路由跳转所需要的信息,比如跳转类型,目标等。Postcard 继承 RouteMeta。
Postcard.java: 直译“跳转卡片”,ARouter.getInstance().build("/xxx/xxxx")返回的就是Postcard对象,主要用来携带bundle跳转参数。
Warehouse.java: 仅包含了几个集合类型(主要是Map类型)的静态变量,这几个静态集合用来保存activity和其path对应关系,以及其分组关系等。
// Cache route and metas static Map<String, Class<? extends IRouteGroup>> groupsIndex = new HashMap<>(); static Map<String, RouteMeta> routes = new HashMap<>();
LogisticsCenter.java: 实现Arouter的初始化,以及对Warehouse类里的几个集合进行填充等。
2.ARouter的注解分析
ARouter定义了好几个注解类,我们暂时只关注@Route注解,它用来标注Activity,设置path。
@Route(path = "/test/activity") public class YourActivity extend Activity { ... }
而@Route注解是使用了APT技术来发挥作用的。
APT即为Annotation Processing Tool,中文意思为编译时注解处理器。它是javac的一个工具。
代码在编译时,APT可以用来扫描和处理注解,获取到注解和被注解对象的相关信息。
而Java是支持自动生成代码的,因此写好逻辑使得编译时根据注解信息自动生成我们需要的类,在运行时就可以使用了。
关于APT的分析,可参考《Android编译期插桩,让程序自己写代码》
那么我们如何自定义生成的代码呢,这就是APT的核心类-AbstractProcessor类的作用了。ARouter源码中有个RouteProcessor.java类,继承自AbstractProcessor类,就是用来处理@Route注解的,重写的process()方法,实现了对@Route注解的处理和自动生成文件代码的逻辑。
这样项目编译之后,就会生成代码了,生成的文件如图:
可见生成了好几个文件。
-
其中以"Arouter$Root$组件名"命名的文件,是用来保存页面路径的分组和路径文件间的对应关系的,
(注意这里其实是两个$,由于markdown语法会对它进行处理,所以我就少写一个以正常显示)
public class ARouter$$Root$$app implements IRouteRoot { @Override public void loadInto(Map<String, Class<? extends IRouteGroup>> routes) { routes.put("app", ARouter$$Group$$app.class); } }
这个类中的loadInto()方法,是如何被调用的呢?
它是在ARouter初始化的时候,即ARouter.init(mApplication) -> LogisticsCenter.init()中被调用的。传入的routes即为Warehouse类的静态变量Warehouse.groupsIndex。
- 而以“Arouter$Root$分组”命名的文件,是用来保存该分组下,页面path和对应的页面关系的。有几个分组,就会有几个“Arouter$Root$分组”文件。
public class ARouter$$Group$$app implements IRouteGroup { @Override public void loadInto(Map<String, RouteMeta> atlas) { atlas.put("/app/main1", RouteMeta.build(RouteType.ACTIVITY, MainActivity.class, "/app/main1", "app", null, -1, -2147483648)); } }
这个类的loadInto()方法,传入的是Warehouse类的另一个静态变量Warehouse.routes,但是此方法不是在ARouter初始化的时候调用,而是在页面跳转的时候,如果满足条件“要跳转的页面是其所在的分组首个要跳转的”才调用,加载完Warehouse.routes后继续跳转页面。
代码调用链:ARouter.getInstance().navigation() -> _ARouter.getInstance().navigation() -> LogisticsCenter.completion() -> IRouteGroup.loadInto(Warehouse.routes)
这样实现了延迟加载,且只加载该页面所属分组的映射关系,减少了不必要的内存耗费。
关于Arouter的页面path分组+按需加载策略讲解,可参考https://www.cnblogs.com/jymblog/p/11698914.html
三、ARouter页面跳转代码流程:
下面以一个常见的跳转代码为例,分析下代码调用流程:
ARouter.getInstance().build("/test/MainActivity") .withString("key", "TestString") .navigation();
下面是ARouter类的build方法:
public Postcard build(String path) { return _ARouter.getInstance().build(path); }
可见build()方法由代理类_Arouter实现。下面是_Arouter类的build()方法:
/**
* Build postcard by path and default group
*/ 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), true); } }
一般情况下我们都不会自定义路径替换逻辑,即不会实现PathReplaceService,因此代码会走到最后一行:return build(path, extractGroup(path), true)
extractGroup(path)这个方法的作用是提取出路径的分组部分,比如页面path为“/test/MainActivity”,则extractGroup(path)得到的结果是“test”,就不贴代码了。继续看:
/**
* Build postcard by path and group
*/ protected Postcard build(String path, String group, Boolean afterReplace) { if (TextUtils.isEmpty(path) || TextUtils.isEmpty(group)) { throw new HandlerException(Consts.TAG + "Parameter is invalid!"); } else { if (!afterReplace) { PathReplaceService pService = ARouter.getInstance().navigation(PathReplaceService.class); if (null != pService) { path = pService.forString(path); } } return new Postcard(path, group); } }
afterReplace为true,因此ARouter.getInstance().build("/test/MainActivity")的运行结果就是new Postcard("/test/MainActivity", “/test”)。
那么ARouter.getInstance().build("/test/MainActivity").withString(“key”, “TestString”).navigation()就是:
new Postcard("/test/MainActivity", “/test”).withString(“key”, “TestString”).navigation()。
看Postcard做了什么处理:
public Postcard() { this(null, null); } public Postcard(String path, String group) { this(path, group, null, null); } public Postcard(String path, String group, Uri uri, Bundle bundle) { setPath(path); setGroup(group); setUri(uri); this.mBundle = (null == bundle ? new Bundle() : bundle); }
public Postcard withString(@Nullable String key, @Nullable String value) { mBundle.putString(key, value); return this; }
可以得到,new Postcard("/test/MainActivity", “/test”).withString(“key”, “TestString”)的处理结果是得到了一个Postcard对象,其成员变量path,group和mBundle都已经赋值。
继续看Postcard的navigation()方法:
public Object navigation() { return navigation(null); } public Object navigation(Context context) { return navigation(context, null); } public Object navigation(Context context, NavigationCallback callback) { return ARouter.getInstance().navigation(context, this, -1, callback); }
ARouter的navigation()方法是由_ARouter实现的,方法较长,这里贴出简化后的代码:
protected Object navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) { try { LogisticsCenter.completion(postcard); } catch (NoRouteFoundException ex) { logger.warning(Consts.TAG, ex.getMessage()); xxx处理 return null; } xxxx处理 return _navigation(context, postcard, requestCode, callback); }
可见,通过LogisticsCenter.completion(postcard)补充了postcard后,传入了_navigation()方法。
_navigation()方法的逻辑很简单,就是根据postcard携带的信息构造出Intent对象等,然后调用ActivityCompat.startActivity()方法启动页面,启动流程就完成了。这里就不贴它代码了。
重点看下LogisticsCenter.completion(postcard)这个方法,之前我们分析的“按需加载”策略就是在这里实现的。
下面是简化版的实现:
public synchronized static void completion(Postcard postcard) { RouteMeta routeMeta = Warehouse.routes.get(postcard.getPath()); if (null == routeMeta) { // 首次跳转此分组的页面时,Warehouse.routes未加载此分组,因此会走到这里 // 此路由信息不存在,或者未加载,所以找不到 Class<? extends IRouteGroup> groupMeta = Warehouse.groupsIndex.get(postcard.getGroup()); // Load route meta. // 加载此分组的path和页面映射关系到Warehouse.routes中 try { IRouteGroup iGroupInstance = groupMeta.getConstructor().newInstance(); iGroupInstance.loadInto(Warehouse.routes); } catch (Exception e) { throw new HandlerException(TAG + "Fatal exception when loading group meta. [" + e.getMessage() + "]"); } completion(postcard); // 再次调用本方法,补充postcard } else { // 非首次跳转此分组的页面,则会走到这里 // 已找到路由信息,直接填充给postcard postcard.setDestination(routeMeta.getDestination()); postcard.setType(routeMeta.getType()); postcard.setPriority(routeMeta.getPriority()); postcard.setExtra(routeMeta.getExtra()); xxx其它信息的添加 } }
经过此方法的处理,跳转的目标页面和携带的参数都已经赋值完成,后续通过_Arouter._navigation()方法启动页面即可。
本文地址:https://blog.csdn.net/fenggering/article/details/107788709