ARouter原理及用法
路由框架在组件化中占很大的作用,目前主流中挑选了ARouter 1.5.0版本进行源码分析。看源码最香的方式就是断点结合文章一起食用。。
简单使用
我们先来看下 ARouter 能干什么:
一、功能介绍
- 支持直接解析标准URL进行跳转,并自动注入参数到目标页面中
- 支持多模块工程使用
- 支持添加多个拦截器,自定义拦截顺序
- 支持依赖注入,可单独作为依赖注入框架使用
- 支持InstantRun
- 支持MultiDex(Google方案)
- 映射关系按组分类、多级管理,按需初始化
- 支持用户指定全局降级与局部降级策略
- 页面、拦截器、服务等组件均自动注册到框架
- 支持多种方式配置转场动画
- 支持获取Fragment
- 完全支持Kotlin以及混编(配置见文末 其他)
- 支持第三方 App 加固(使用 arouter-register 实现自动注册)
- 支持生成路由文档
- 提供 IDE 插件便捷的关联路径和目标类
- 支持增量编译(开启文档生成后无法增量编译)
- 支持动态注册路由信息
二、典型应用
- 从外部URL映射到内部页面,以及参数传递与解析
- 跨模块页面跳转,模块间解耦
- 拦截跳转过程,处理登陆、埋点等逻辑
- 跨模块API调用,通过控制反转来做组件解耦
\
以上是直接从 官方文档 复制过来的,根据 ARouter 支持的能力和使用场景,我们就能知道什么时候怎么用,官网已经很详细介绍了使用,不再赘述。
源码分析
Activity 跳转
activity的跳转绝对是最常见的场景,我们直接从跳转代码入手,以下是代码示例:
// Activity
@Route(path = "/test/router")
public class RouterTestActivity extends Activity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ARouter.getInstance().inject(this);
getIntent().getExtras().getInt("testStr");
getIntent().getExtras().getInt("testInt");
}
}
// 跳转
ARouter.getInstance().build("/test/router")
.withString("testStr", "testtesttest")
.withInt("testInt", 1)
.navigation()
直接点进 build 方法看一眼:
protected Postcard build(String path) {
if (TextUtils.isEmpty(path)) {
throw new HandlerException(Consts.TAG + "Parameter is invalid!");
} else {
// 这里不展开讲了,可以看到ARouter是通过实现PathReplaceService支持路由协议替换的
PathReplaceService pService = ARouter.getInstance().navigation(PathReplaceService.class);
if (null != pService) {
path = pService.forString(path);
}
return build(path, extractGroup(path));
}
}
这里有个路由协议替换的小技巧已用注释说明。extractGroup 方法也不用展开讲,作用就是对路由协议分组,就上述的例子 /test/router
的 group 就是 test 如果后续还有 /test/router2
这样的路径,都会被分配到 test 组下。继续进 build 方法查看:
protected Postcard build(String path, String group) {
if (TextUtils.isEmpty(path) || TextUtils.isEmpty(group)) {
throw new HandlerException(Consts.TAG + "Parameter is invalid!");
} else {
PathReplaceService pService = ARouter.getInstance().navigation(PathReplaceService.class);
if (null != pService) {
path = pService.forString(path);
}
return new Postcard(path, group);
}
}
貌似这 build 有点重复的嫌疑啊,好像就多了个 group 是否为空的判断?不管了,最后会创建出一个 Postcard 对象,这个对象相当重要,我们之后在继续看看。好了,现在 build 方法构建出了 postcard 对象,然后调用了 navigation 方法,然后 navigation 会有一连串的重载方法,最后调用:
protected Object navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
// 省略...
try {
LogisticsCenter.completion(postcard);
} catch (NoRouteFoundException ex) {
// ...
}
// 拦截逻辑
if (!postcard.isGreenChannel()) { // It must be run in async thread, maybe interceptor cost too mush time made ANR.
interceptorService.doInterceptions(postcard, new InterceptorCallback() {
@Override
public void onContinue(Postcard postcard) {
_navigation(context, postcard, requestCode, callback);
}
public void onInterrupt(Throwable exception) {
if (null != callback) {
callback.onInterrupt(postcard);
}
logger.info(Consts.TAG, "Navigation failed, termination by interceptor : " + exception.getMessage());
}
});
} else {
return _navigation(context, postcard, requestCode, callback);
}
return null;
}
拦截逻辑暂时不详细描述,拦截器通过后会调用 _navigation
,先跟进看LogisticsCenter.completion(postcard)
,这是一个关键方法:
public synchronized static void completion(Postcard postcard) {
// 第一次用path获取routeMeta会是空的
RouteMeta routeMeta = Warehouse.routes.get(postcard.getPath());
if (null == routeMeta) { // Maybe its does't exist, or didn't load.
// group对应的groupMeta已经生成,在本例中就是test组。
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 {
try {
// 实例化groupMeta并将其所有的routeMeta添加到Warehouse.routes表内
IRouteGroup iGroupInstance = groupMeta.getConstructor().newInstance();
iGroupInstance.loadInto(Warehouse.routes);
Warehouse.groupsIndex.remove(postcard.getGroup());
} catch (Exception e) {
throw new HandlerException(TAG + "Fatal exception when loading group meta. [" + e.getMessage() + "]");
}
// 递归之后会走到下方的else逻辑
completion(postcard); // Reload
}
} else {
// 把routerMeta里的属性都添加到postcard
postcard.setDestination(routeMeta.getDestination());
postcard.setType(routeMeta.getType());
postcard.setPriority(routeMeta.getPriority());
postcard.setExtra(routeMeta.getExtra());
// uri跳转逻辑这里就不分析,只分析普通的path跳转。
Uri rawUri = postcard.getUri();
// ......
}
}
注释已经解释了这块代码的逻辑,但是在这里最好断点,才能理解这个递归的逻辑。结束了对postcard的丰富,继续进入 _navigation
:
private Object _navigation(Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
final Context currentContext = null == context ? mContext : context;
switch(postcard.getType()) {
case ACTIVITY:
final Intent intent = new Intent(currentContext, postcard.getDestination());
intent.putExtras(postcard.getExtras());
int flags = postcard.getFlags();
if (-1 != flags) {
intent.setFlags(flags);
} else if (!(currentContext instanceof Activity)) {
intent.setFlags(268435456);
}
String action = postcard.getAction();
if (!TextUtils.isEmpty(action)) {
intent.setAction(action);
}
this.runInMainThread(new Runnable() {
public void run() {
_ARouter.this.startActivity(requestCode, currentContext, intent, postcard, callback);
}
});
return null;
// ......
}
}
终于看到最后 intent 跳转了,postcard.getExtras()
里带的就是 withString
, withInt
这类方法所传进来的参数塞到 Bundle 里。
前面通过断点我们看到了如何去拿到 routerMeta 并把其属性赋给 postcard,从而才有了跳转的 className。这里我们就需要看下编译期如何生成代码的。
代码生成
如果对编译期代码生成完全不了解的,可以先去看一些文章,我这边也有简单写过一篇和编译期生成代码有关的文章 EventBus 3.0 从编译时注解分析源码 。
源码可直接在 官方文档 arouter-compiler 模块看或者下过来看。先给出本例中生成的代码:
public class ARouter$$Group$$test implements IRouteGroup {
@Override
public void loadInto(Map<String, RouteMeta> atlas) {
atlas.put("/test/router", RouteMeta.build(RouteType.ACTIVITY, RouterTestActivity.class, "/test/router", "test", null, -1, -2147483648));
}
}
看到 loadInto
就很熟悉了,就是这个方法把 routeMeta 添加到表中。
@AutoService(Processor.class)
@SupportedAnnotationTypes({ANNOTATION_TYPE_ROUTE, ANNOTATION_TYPE_AUTOWIRED})
public class RouteProcessor extends BaseProcessor {
private Map<String, Set<RouteMeta>> groupMap = new HashMap<>(); // ModuleName and routeMeta.
private Map<String, String> rootMap = new TreeMap<>(); // Map of root metas, used for generate class file in order.
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
if (CollectionUtils.isNotEmpty(annotations)) {
Set<? extends Element> routeElements = roundEnv.getElementsAnnotatedWith(Route.class);
try {
logger.info(">>> Found routes, start... <<<");
this.parseRoutes(routeElements);
} catch (Exception e) {
logger.error(e);
}
return true;
}
return false;
}
}
BaseProcessor
抽象类实际上是继承了 AbstractProcessor
,而编译期会扫描所有继承 AbstractProcessor
的类执行 process 方法。看到 process 方法突然豁然开朗,第一行代码就是拿到所有被 @Route 注解的节点,看看具体在 parseRoutes
里是如何解析的,这边只留了对 Activity 处理代码,否则代码量太多了。
private void parseRoutes(Set<? extends Element> routeElements) throws IOException {
if (CollectionUtils.isNotEmpty(routeElements)) {
TypeMirror type_Activity = elementUtils.getTypeElement(ACTIVITY).asType();
// root是当前模块的所有的group,可以先看 group 生成的逻辑
MethodSpec.Builder loadIntoMethodOfRootBuilder = MethodSpec.methodBuilder(METHOD_LOAD_INTO)
.addAnnotation(Override.class)
.addModifiers(PUBLIC)
.addParameter(rootParamSpec);
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)) {
if (types.isSubtype(tm, type_Activity)) {
// 实例化 RouteMeta 对象
routeMeta = new RouteMeta(route, element, RouteType.ACTIVITY, paramsType);
} else {
routeMeta = new RouteMeta(route, element, RouteType.parse(FRAGMENT), paramsType);
}
}
// 将 routeMeta 分组保存到 groupMap 中,后续遍历有用到
categories(routeMeta);
}
Map<String, List<RouteDoc>> docSource = new HashMap<>();
// 开始遍历 groupMap
for (Map.Entry<String, Set<RouteMeta>> entry : groupMap.entrySet()) {
String groupName = entry.getKey();
// 构建loadInfo方法
MethodSpec.Builder loadIntoMethodOfGroupBuilder = MethodSpec.methodBuilder(METHOD_LOAD_INTO)
.addAnnotation(Override.class)
.addModifiers(PUBLIC)
.addParameter(groupParamSpec);
Set<RouteMeta> groupData = entry.getValue();
// 所有该组下 routeMeta 都会被添加到表中
for (RouteMeta routeMeta : groupData) {
ClassName className = ClassName.get((TypeElement) routeMeta.getRawType());
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());
}
// 这一步是生成group的java文件,写入之前的代码。kotlin会有单独的compiler处理
String groupFileName = NAME_OF_GROUP + groupName;
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);
rootMap.put(groupName, groupFileName);
}
if (MapUtils.isNotEmpty(rootMap)) {
// 写入代码,routes表添加group对应的className
for (Map.Entry<String, String> entry : rootMap.entrySet()) {
loadIntoMethodOfRootBuilder.addStatement("routes.put($S, $T.class)", entry.getKey(), ClassName.get(PACKAGE_OF_GENERATE_FILE, entry.getValue()));
}
}
// 这一步是生成root的java文件
String rootFileName = NAME_OF_ROOT + SEPARATOR + moduleName;
JavaFile.builder(PACKAGE_OF_GENERATE_FILE,
TypeSpec.classBuilder(rootFileName)
.addJavadoc(WARNING_TIPS)
.addSuperinterface(ClassName.get(elementUtils.getTypeElement(ITROUTE_ROOT)))
.addModifiers(PUBLIC)
.addMethod(loadIntoMethodOfRootBuilder.build())
.build()
).build().writeTo(mFiler);
}
}
好了,我们把核心的生成代码提取了出来,根据注释先看完这一块代码,然后会发现生成了两个文件,第一个是和 group 有关的文件,这代码我们已经在前面贴出来了,会递归中找到 group 然后把其添加到 routeMeta 表中用来提供 className, 携带参数等。还有一个文件是 root 相关的文件,它是用来存放该模块下所有的group对象的,在前面的递归找到 group 我们并不清楚为什么 Warehouse.groupsIndex
会有数据,其实这是 root 文件的功劳:
public class ARouter$$Root$$demoApp implements IRouteRoot {
@Override
public void loadInto(Map<String, Class<? extends IRouteGroup>> routes) {
routes.put("test", ARouter$$Group$$test.class);
}
}
root 文件内添加了所有 group 的 class对象,我们看下 ARouter.init
初始化方法干了什么:
public synchronized static void init(Context context, ThreadPoolExecutor tpe) throws HandlerException {
// routerMap是从本地文件内读取的,文件内保存的是所有group的className
for (String className : routerMap) {
if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_ROOT)) {
// 这里就是实例化group对象并添加到 Warehouse.groupsIndex
((IRouteRoot) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.groupsIndex);
}
}
}
这样整个脉络就理清楚了 group 对象表和 route 对象表是如何添加的,弄清楚了这个,intent跳转就很简单了。
高级用法
相信很多人用 ARouter 基本就是跳转Activity或获取Fragment等,这里介绍几个其他稍微高级点的用法。看源码按照上节那样断点看就完事了。
Autowired
@Route(path = "/test/fragment")
public class RouterTestFragment extends Fragment {
@Autowired
String testStr;
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
ARouter.getInstance().inject(this);
Log.d("RouterTestFragment", "testStr -> " + testStr);
return super.onCreateView(inflater, container, savedInstanceState);
}
}
// 调用
ARouter.getInstance().build("/test/fragment")
.withString("testStr", "testtest")
.navigation();
@Autowired 这个注解能比较方便的获取上一个页面传递过来的参数,原理上也是通过注解在编译时生成代码文件,这里就不详细解析生成文件的源码了,实际上本案例生成的代码如下:
public class RouterTestFragment$$ARouter$$Autowired implements ISyringe {
private SerializationService serializationService;
@Override
public void inject(Object target) {
serializationService = ARouter.getInstance().navigation(SerializationService.class);
RouterTestFragment substitute = (RouterTestFragment)target;
substitute.testStr = substitute.getArguments().getString("testStr");
}
}
然后在调用 ARouter.getInstance().inject(this);
时就会通过反射调用到上述的 inject 方法,最后拿到 testStr 参数值。
Provider
provider 也是很常用的一个能力,除了路由跳转之外,很多时候我们需要获取数据、调用api。那这个时候 provider 就派上用场了。
// 实现IProvider的接口放到接口层
public interface IProviderService extends IProvider {
void test();
}
// 实现类放到实现层
@Route(path = "/test/provider")
public class TestProviderService implements IProviderService {
@Override
public void test() {
Log.d("TestProviderService", "test");
}
@Override
public void init(Context context) {
Log.d("TestProviderService", "init");
}
}
// 使用方式一
@Autowired(name = "/test/provider")
IProviderService providerService;
providerService.test();
// 使用方式二
IProviderService providerService = (IProviderService)ARouter.getInstance().build("/test/provider").navigation();
providerService.test();
provider 也可以和 @Autowired 注解配合使用。这里需要注意的 IProvider 的 init 方法在应用生命周期内只会调用一次,第二次 provider 实力从缓存中获取就不会再调用 init 方法了。至于它的原理也不用多赘述,还是一样在编译时生成代码,并通过反射拿到实例。
Interceptor
@Interceptor(priority = 1)
public class TestInterceptor implements IInterceptor {
@Override
public void process(Postcard postcard, InterceptorCallback callback) {
if("/test/router".equals(postcard.getPath())) {
Log.d("TestInterceptor", "/test/router is intercepted");
}
callback.onContinue(postcard);
}
@Override
public void init(Context context) {
Log.d("TestInterceptor", "init");
}
}
拦截器的注解 @Interceptor 通过也是在编译时扫描生成代码,并且拦截器在 ARouter.init 的时候就会调用其 init方法,每个拦截器都会走 process 方法,我们做想做的事就可以了。源码也不再解析,拦截器也是很好用的一个能力。
总结
ARouter对于大多数组件化项目来说是核心的解耦模块,更多的高级使用可以参考 官方文档 。在使用过程中肯定也会遇到很多的坑和不足,需要慢慢总结。
Tips
- 建议大项目的ARouter需单独设置线程池,ARouter.getInstance().setExecutor(),默认的线程池带有核心线程,创建线程后不会被回收。
- 支持传递requestCode并重写onActivityResult方法,但这个处理只能在Activity内做,如果需要在任何类支持回调函数,需要我们自己对ARouter封装一层。
- ARouter是编译期生成代码的,跳转不了页面或者获取不到provider等情况,大部分可能是生成代码部分的问题,要学会看是否有生成代码了、生成代码里是否注册了路由。如果是这个问题,那么看下配置ARouter模块时是否有问题。