欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

ARouter路由框架原理简单分析

程序员文章站 2022-11-20 14:46:21
前言ARouter是阿里巴巴推出的一款Android路由框架,官方介绍:一个用于帮助 Android App 进行组件化改造的框架,支持模块间的路由、通信、解耦。github文档地址:https://github.com/alibaba/ARouter/blob/master/README_CN.md一、ARouter解决的问题一般我们使用ARouter的目的,是为了解决组件间activity跳转的问题。众所周知,如果从Activity A跳转到B,原始写法可以这么写:startActivity...

前言

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路由框架原理简单分析

这样项目编译之后,就会生成代码了,生成的文件如图:
ARouter路由框架原理简单分析

可见生成了好几个文件。

  • 其中以"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