阿里开源路由框架ARouter的源码分析

  • 组件化:随着业务量的不断增长,app也会不断的膨胀,开发团队的规模和工作量也会逐渐增大,面对所衍生的64K问题、协作开发问题等,app一般都会走向组件化。组件化就是将APP按照一定的功能和业务拆分成多个组件module,不同的组件独立开发,组件化不仅能够提供团队的工作效率,还能够提高应用性能。而组件化的前提就是解耦,那么我们首先要做的就是解耦页面之间的依赖关系

  • Native与H5的问题:现在的APP很少是纯Native的,也很少会有纯H5的,一般情况下都是将两者进行结合。这时候就需要非常便捷并且统一的跳转方案,因为在H5中是无法使用StartActivity()跳转到Native页面的,而从Native跳转到H5页面也只能通过配置浏览器的方式实现

  • 其他

如果开发团队面临了以上的问题,那么就是时候选择一款实用的自定义路由框架了

当然,如果以上问题并没有成为一定规模,那么使用替代方案实现也无可厚非,但是我们也要意识到他们的不足

  • 方案1:文本记录Class全路径,使用反射方式实例Class从而实现跳转
    • 运行时反射,会导致性能下降,尤其跳转是一个高发动作
    • Class全路径的文本记录会增加维护成本
  • 方案2:为所有Activity的配置Action,并文本记录从而实现跳转
    • Action的文本记录会增加维护成本
    • 无法动态自定义所携带参数

1.3 对自定义路由框架的设想

【图1】
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

我们期望自定义路由框架能够满足Android原生方案的不足之处,比如:

  • 通过URL索引就可以解决类依赖的问题;
  • 通过分布式管理页面配置可以解决隐式intent中集中式管理Path的问题;
  • 自己实现整个路由过程也可以拥有良好的扩展性
  • 通过AOP的方式解决跳转过程无法控制的问题
  • 能够提供非常灵活的降级方式

总的来说,一款路由框架至少要满足以下3个功能:

  1. 分发:把一个URL或者请求按照一定的规则分配给一个服务或者页面来处理,这个流程就是分发,分发是路由框架最基本的功能,当然也可以理解成为简单的跳转。
  2. 管理:将组件和页面按照一定的规则管理起来,在分发的时候提供搜索、加载、修改等操作,这部分就是管理,也是路由框架的基础,上层功能都是建立在管理之上。
  3. 控制:就像路由器一样,路由的过程中,会有限速、屏蔽等一些控制操作,路由框架也需要在路由的过程中,对路由操作做一些定制性的扩展,比方刚才提到的AOP,后期的功能更新,也是围绕这个部分来做的。

二、ARouter的概述

ARouter不仅实现了对基础组件的路由,而且其具有的Ioc功能也实现了对动作的路由,也就是跨模块API调用!

可以理解为“通过URL找Class能实现的业务,ARouter都能实现”,这个概念要理解透彻!

我们在选择一款开源框架的时候,要综合考量开发团队质量、后期维护、功能需求等诸多方面,在综合对比之后ARouter脱颖而出

作为阿里开发团队的产品,ARouter在2016年年底就已经出现,并在2017年3月份的技术峰会上由阿里云资深开发工程师刘志龙公开分享,目前在GitHub具有2k+ star, ARouter的开发团队质量、后期维护都是相对比较稳定的

ARouter的优势:

  • 使用注解,实现了映射关系自动注册 与 分布式路由管理
  • 编译期间处理注解,并生成映射文件,没有使用反射,不影响运行时性能
  • 映射关系按组分类、多级管理,按需初始化
  • 灵活的降级策略,每次跳转都会回调跳转结果,避免StartActivity()一旦失败将会抛出运营级异常
  • 自定义拦截器,自定义拦截顺序,可以对路由进行拦截,比如登录判断和埋点处理
  • 支持依赖注入,可单独作为依赖注入框架使用,从而实现 跨模块API调用 == 对动作的路由
  • 支持直接解析标准URL进行跳转,并自动注入参数到目标页面中
  • 支持获取Fragment
  • 支持多模块使用,支持组件化开发
  • 支持多种方式配置转场动画
  • 支持MultiDex(Google方案)

ARouter的劣势:

  • 不支持插件化 [已纳入开发计划]
  • 不支持生成可读的映射关系文档 [已纳入开发计划]
  • 不支持 多路径
  • 不支持 手动分配路由
  • 不支持 自定义路由匹配规则

三、ARouter的引入和使用

官方文档已经很清晰易懂,详见github.com/alibaba/ARo…

在这里简单列举下:

  • 界面跳转类

ARouter.getInstance().build(“/test/activity2”).navigation();

ARouter.getInstance().build(“/test/activity2”).navigation(this, requestCode);

/*这种使用URi的方式中,URi的Scheme 和 host不影响结果,可以随便设,关键的是path

    • build(URI)会把URI解析为path,并把当前URI存入PostCard
    • build(String)构造的PostCard不存储URI*/
      Uri testUriMix = Uri.parse(“xx://xxx/test/activity2”);
      ARouter.getInstance().build(testUriMix)
      .withString(“name”, “老王”)
      .withInt(“age”, 18)
      .withBoolean(“boy”, true)
      .withLong(“high”, 180)
      .withString(“url”, “https://a.b.c”)
      .withParcelable(“pac”, testParcelable)
      .withObject(“obj”, testObj)
      .navigation();

ARouter.getInstance().build(“/test/activity2”).navigation(Context mContext, int requestCode, NavigationCallback callback);

  • Ioc依赖注入类

//【ByName方式】根据 注解的name对HelloService进行注入
((HelloService) ARouter.getInstance().build(“/service/hello”).navigation()).sayHello(“mike”);

//【ByType方式】仅在HelloService接口只有一个实现时可用 = 根据classType实现注入
//当同一接口有多个实现的时候,必须使用byName的方式发现服务
ARouter.getInstance().navigation(HelloService.class).sayHello(“mike”);

//目标类直接注入
ARouter.getInstance().navigation(SingleService.class).sayHello(“Mike”);

  • 降解策略类

//【1】实现DegradeService(实现了全局降级逻辑, IOC.byType方式被_ARouter调用)

//【2】单次,使用NavigationCallback

  • 其他

ARouter.openLog();
ARouter.openDebug();//影响InstantRun + 打印路由表
ARouter.init(getApplication());
ARouter.getInstance().destroy();

四、源码分析

强烈要求:在阅读本文前详细阅读官方文档:开源最佳实践:Android平台页面路由框架ARouter,并了解以下概念:

  • 编译器处理注解产生映射文件,运行期加载映射文件实现路由
  • Bootstrapping、Extensibility以及Simple & Enough
  • 编译期间:页面自动注册—注解&注解处理器
  • 运行期间:动态加载—–分组管理,按需加载
  • 全局拦截器
  • 依赖注入
  • 运行期动态修改路由
  • 降级问题

本文约定:路由 = 路径URL : 目标类Class

  • arouter-annotation: ARouter路由框架所使用的全部注解,及其相关类
  • arouter-compiler:注解编译处理器,引入“arouter-annotation”,在编译器把注解标注的相关目标类生成映射文件
  • arouter-api:实现路由控制

总的来说,就是arouter-annotation实现了路由表结构的定义,arouter-compiler在编译期完成了 构造路由表逻辑的创建, arouter-api在运行期加载逻辑构建路由表,并实现路由控制

4.1 arouter-annotation注解

【图4.1.1】
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

ARouter所提出的“分布式控制”就是通过不同目标Class的注解实现的,我们来看看ARouter都定义了那些注解:

4.1.1 @Route路由注解

@Route 是 ARouter 最重要的注解,也是路由最基本的节点,该注解主要用于描述路由中的路径URL信息,使用该注解标注的类将被自动添加至路由表中。

值得说明的一点是 ARouter 并非仅提供页面(Activity)的路由功能,还可以用来路由模块想要暴露给其他模块调用的接口。

也就是说 @Route 不仅可用于 Activity 类,还可用于模块对外接口的实现类,实现类似于 AIDL 的功能,也就是IOC

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.CLASS)
public @interface Route {

//路径URL字符串
String path();

//组名,默认为一级路径名;一旦被设置,跳转时必须赋值
String group() default “”;

//该路径的名称,用于产生JavaDoc
String name() default “undefined”;

//额外配置的开关信息;譬如某些页面是否需要网络校验、登录校验等
int extras() default Integer.MIN_VALUE;

//该路径的优先级
int priority() default -1;
}

4.1.2 @Interceptor拦截器注解

@Interceptor 是拦截器注解,拦截器是全应用全局的,不分module,只要集成进apk就起效

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.CLASS)
public @interface Interceptor {
//该拦截器的优先级
int priority();

//该拦截器的名称,用于产生JavaDoc
String name() default “Default”;
}

4.1.3 @Autowired自动装载注解

@Autowired 是页面跳转时参数传递用的。目标Class中使用该注解标志的变量,会在页面被路由打开的时候,在调用Inject(“`)后自动赋予传递的参数值

@Target({ElementType.FIELD})
@Retention(RetentionPolicy.CLASS)
public @interface Autowired {

// Mark param’s name or service name.
String name() default “”;

// If required, app will be crash when value is null.
// Primitive type wont be check!
boolean required() default false;

// Description of the field
String desc() default “No desc.”;
}

4.1.4 RouteMeta路由元信息

如果全部路由信息认为是一张表格,那么RouteMeta就是表格的一行,代表路由表的一条元信息。

public class RouteMeta {
private RouteType type; // 路由的类型:
private Element rawType; // Raw type of route
private Class<?> destination; // 目标Class
private String path; // 路径URL
private String group; // 分组
private int priority = -1; // 路由的优先级
private int extra; // 目标Class的额外配置开关信息;譬如某些页面是否需要网络校验、登录校验等
private Map<String, Integer> paramsType; // 目标Class的需要注入的参数 的参数名称:参数类型TypeKind
}

public enum RouteType {// 路由的类型
ACTIVITY(0, “android.app.Activity”),
SERVICE(1, “android.app.Service”),
PROVIDER(2, “com.alibaba.android.arouter.facade.template.IProvider”),
CONTENT_PROVIDER(-1, “android.app.ContentProvider”),
BOARDCAST(-1, “”),
METHOD(-1, “”),
FRAGMENT(-1, “android.app.Fragment”),
UNKNOWN(-1, “Unknown route type”);

int id;
String className;
}

public enum TypeKind { //目标Class的需要注入的参数的参数类型
//基本类型
BOOLEAN,
BYTE,
SHORT,
INT,
LONG,
CHAR,
FLOAT,
DOUBLE,

//封装类型
STRING,
PARCELABLE,
OBJECT; //使用Json解析
}

4.2 arouter-compiler注解编译器

实现了“自动注册映射关系”也就是在编译期间自动生成映射文件,所以该module其实就是实现了一些注解处理器,目标在于生成映射文件与辅助文件(构造路由表逻辑的创建)

开源最佳实践:Android平台页面路由框架ARouter中“二、ARouter的技术方案”的“页面注册:注解&注解处理器”
1. 首先通过注解处理器扫出被标注的类文件;
2. 然后按照不同种类的源文件进行分类,这是因为ARouter是一个框架,其能够提供的功能非常多,所以不仅仅提供了跳转功能,它也能够实现模块之间的解耦,除此之外ARouter还能够提供很多的功能,像刚才提到的拦截器可以实现自动注册,其实ARouter中的所有组件都是自动注册的
3. 在按照不同种类的源文件进行分类完成之后,就能够按照固定的命名格式(工程名+ + G r o u p + +Group+ +Group++分组名)生成映射文件,这部分完成之后就意味着编译期的部分已经结束了

一般来说arouter-compiler所产生的class文件有以下几种:

  • 项目目录下的 XXX 工程名 工程名 工程名Autowired. 为自动装载的辅助类,核心为inject函数,实现对目标对象的成员变量的赋值

  • routes目录下

  • 工程名 G r o u p Group Group分组名 【组内的路由清单列表】
    (Map< String, RouteMeta> atlas
    包含了对应分组下的,路由URL与目标对象Class的映射关系;
    注意Router注解中无分组的话,默认以“/xx/xx”的第一个xx为分组名

  • 工程名 R o o t Root Root$模块名 【组别的清单列表】
    Map< String, Class< ? extends IRouteGroup>> routes
    包含了组名与对应组内的路由清单列表Class的映射关系
    是Arouter的“分组管理,按需加载”的实现。
    ARouter在初始化的时候只会一次性地加载所有的root结点,而不会加载任何一个Group结点,这样就会极大地降低初始化时加载结点的数量
    那么什么时候加载分组结点呢?其实就是当某一个分组下的某一个页面第一次被访问的时候,整个分组的全部页面都会被加载进去,这就是ARouter的按需加载

  • 工程名 P r o v i d e r s Providers Providers模块名 【Ioc的动作路由清单列表】
    Map< String, RouteMeta> providers
    PROVIDER 类型的路由节点的清单列表
    包含了使用依赖注入方式的某class(实现了IProvide接口的直接子类)的 路由URL 与class映射关系
    目标Class都实现了IProvider接口,借此实现部分路由转到该清单中
    需要注意的是:Ioc动作路由清单其实只是 Route注解的一种特殊用法,总的来说,还是一种URL与目标类的映射关系
    其实想一下,依赖注入,无非也就是指定好目标接口的目标类,然而实例化后进行赋值。URL就是指定说明

  • 工程名 I n t e r c e p t o r s Interceptors Interceptors模块名 【模块内的拦截器清单列表】
    Map< Integer, Class< ? extends IInterceptor>> interceptors
    包含了某个模块下的拦截器 与 优先级的映射关系
    一个模块下的所有拦截器都在该类中包含,无分组特性,所以直接以模块名命名类文件

  • 映射文件的生成并不是随意的,而是遵循一定的规则。是参照API Module的Temple的接口,进行创建的
  • 可以认为像是一个人一样,他记录了API Module-Temple中接口的名称,函数的名称参数等信息,然后借用一定的工具,写出了继承这些函数的接口
  • 注意:上述描述中,我们说的是“写出了接口”,就类似于在txt文档中写了一些文本,并不涉及引用,这也就意味着不需要依赖API Module,同时也标示API Module的Template中的模板不能随意更改

4.2.1 Route注解处理

区分路由类型是通过“是否为 Activity 、IProvider 、 Service 的子类”来判断的,详见“com.alibaba.android.arouter.compiler.processor.RouteProcessor”

在app中所定义的路由路径有:

@Route(path = “/test/activity1”)
public class Test1Activity extends AppCompatActivity {

}

@Route(path = “/test/activity2”)
public class Test2Activity extends AppCompatActivity {

}

@Route(path = “/test/activity3”)
public class Test3Activity extends AppCompatActivity {

}

@Route(path = “/test/activity4”)
public class Test4Activity extends AppCompatActivity {

}

/以下为Ioc的应用,都实现了IProvider接口**************/
@Route(path = “/service/hello”)
public class HelloServiceImpl implements HelloService {

}

@Route(path = “/service/json”)
public class JsonServiceImpl implements SerializationService {

}

@Route(path = “/service/single”)
public class SingleService implements IProvider {

}

我们先看下使用 annotationProcessor ‘com.alibaba:arouter-compiler:1.0.3’ 处理官方Demo的app所生成的映射文件:
【图4.2.1】
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

很明显,ARouter G r o u p Group Groupservice和ARouter G r o u p Group Grouptest分别是分组 service 和 test 的组内路由清单列表,我们可以把它想象成分别是 1班 和 2 班 的学生名单。
ARouter R o o t Root Rootapp 则是组别的清单列表,我们亦可以把它想成班级的清单,里面有两个班级,1班 和 2 班。

4.2.1.1 工程名 G r o u p Group Group分组名 【组内的路由清单列表】

public class ARouter$$Group$$service implements IRouteGroup {
public ARouter$$Group$$service() {
}

public void loadInto(Map<String, RouteMeta> atlas) {
atlas.put(“/service/hello”, RouteMeta.build(RouteType.PROVIDER, HelloServiceImpl.class, “/service/hello”, “service”, (Map)null, -1, -2147483648));
atlas.put(“/service/json”, RouteMeta.build(RouteType.PROVIDER, JsonServiceImpl.class, “/service/json”, “service”, (Map)null, -1, -2147483648));
atlas.put(“/service/single”, RouteMeta.build(RouteType.PROVIDER, SingleService.class, “/service/single”, “service”, (Map)null, -1, -2147483648));
}
}

public class ARouter$$Group$$test implements IRouteGroup {
public ARouter$$Group$$test() {
}

public void loadInto(Map<String, RouteMeta> atlas) {
atlas.put(“/test/activity1”, RouteMeta.build(RouteType.ACTIVITY, Test1Activity.class, “/test/activity1”, “test”, new HashMap() {
{
this.put(“pac”, Integer.valueOf(9));
this.put(“obj”, Integer.valueOf(10));
this.put(“name”, Integer.valueOf(8));
this.put(“boy”, Integer.valueOf(0));
this.put(“age”, Integer.valueOf(3));
this.put(“url”, Integer.valueOf(8));
}
}, -1, -2147483648));
atlas.put(“/test/activity2”, RouteMeta.build(RouteType.ACTIVITY, Test2Activity.class, “/test/activity2”, “test”, new HashMap() {
{
this.put(“key1”, Integer.valueOf(8));
}
}, -1, -2147483648));
atlas.put(“/test/activity3”, RouteMeta.build(RouteType.ACTIVITY, Test3Activity.class, “/test/activity3”, “test”, new HashMap() {
{
this.put(“name”, Integer.valueOf(8));
this.put(“boy”, Integer.valueOf(0));
this.put(“age”, Integer.valueOf(3));
}
}, -1, -2147483648));
atlas.put(“/test/activity4”, RouteMeta.build(RouteType.ACTIVITY, Test4Activity.class, “/test/activity4”, “test”, (Map)null, -1, -2147483648));
atlas.put(“/test/fragment”, RouteMeta.build(RouteType.FRAGMENT, BlankFragment.class, “/test/fragment”, “test”, (Map)null, -1, -2147483648));
atlas.put(“/test/webview”, RouteMeta.build(RouteType.ACTIVITY, TestWebview.class, “/test/webview”, “test”, (Map)null, -1, -2147483648));
}
}

4.2.1.2 工程名 R o o t Root Root$模块名 【组别的清单列表】

public class ARouter$$Root$$app implements IRouteRoot {
public ARouter$$Root$$app() {
}

public void loadInto(Map<String, Class<? extends IRouteGroup>> routes) {
routes.put(“service”, service.class);
routes.put(“test”, test.class);
}
}

4.2.1.3 工程名 P r o v i d e r s Providers Providers模块名 【Ioc的动作路由清单列表】

特别的,请注意:PROVIDER 类型的路由节点既存在于存在于对应的分组中,也存在于该类型的清单列表中

这就好像学生中有一些是少先队员,每个班级中都有可能有少先队员,而学校又有一份少先队员的总列表。这就意味着我们有两种方式来查找到一个可确定的少先队员。

所以,ARouter 可通过两种方式来获取 PROVIDER 类型的路由节点

  • ByName的方式:通过【组内的路由清单列表】
    根据 注解的name对HelloService进行注入
    ((HelloService) ARouter.getInstance().build(“/service/hello”).navigation()).sayHello(“mike”);
  • ByType的方式 :通过【Ioc的动作路由清单列表】
    仅在HelloService接口只有一个实现时可用 = 根据classType实现注入
    当同一接口有多个实现的时候,必须使用byName的方式发现服务
    ARouter.getInstance().navigation(HelloService.class).sayHello(“mike”);

注意名称是 (实现了IProvide接口的直接子类)
- HelloServiceImpl的名称是 其父类HelloService,HelloService实现了IProvide接口
- JsonServiceImpl的名称是 其父类SerializationService,SerializationService实现了IProvide接口
- SingleService的名称是 自身SingleService,SingleService实现了IProvide接口

public class ARouter$$Providers$$app implements IProviderGroup {
public ARouter$$Providers$$app() {
}

public void loadInto(Map<String, RouteMeta> providers) {
providers.put(“com.alibaba.android.arouter.demo.testservice.HelloService”, RouteMeta.build(RouteType.PROVIDER, HelloServiceImpl.class, “/service/hello”, “service”, (Map)null, -1, -2147483648));
providers.put(“com.alibaba.android.arouter.facade.service.SerializationService”, RouteMeta.build(RouteType.PROVIDER, JsonServiceImpl.class, “/service/json”, “service”, (Map)null, -1, -2147483648));
providers.put(“com.alibaba.android.arouter.demo.testservice.SingleService”, RouteMeta.build(RouteType.PROVIDER, SingleService.class, “/service/single”, “service”, (Map)null, -1, -2147483648));
}
}

4.2.2 Autowired注解处理

图4.2.1并没有显示自动装载的辅助类,是由于test-module-1中没有使用 Autowired 注解,最后我们再给出下app的映射文件结构
【图4.2.2】
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

@Route(path = “/test/activity1”)
public class Test1Activity extends AppCompatActivity {

@Autowired
String name;

@Autowired
int age;

@Autowired(name = “boy”)
boolean girl;

@Autowired
TestParcelable pac;

@Autowired
TestObj obj;

private long high;

@Autowired
String url;

@Autowired
HelloService helloService;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test1);

ARouter.getInstance().inject(this);

// name = getIntent().getStringExtra(“name”);
// age = getIntent().getIntExtra(“age”, 0);
// girl = getIntent().getBooleanExtra(“girl”, false);
// high = getIntent().getLongExtra(“high”, 0);
// url = getIntent().getStringExtra(“url”);

String params = String.format(
“name=%s,\n age=%s,\n girl=%s,\n high=%s,\n url=%s,\n pac=%s,\n obj=%s”,
name,
age,
girl,
high,
url,
pac,
obj
);
helloService.sayHello(“Hello moto.”);

((TextView)findViewById(R.id.test)).setText("I am " + Test1Activity.class.getName());
((TextView)findViewById(R.id.test2)).setText(params);
}
}

public class Test1Activity$$ARouter$$Autowired implements ISyringe {
private SerializationService serializationService;

public Test1Activity$$ARouter$$Autowired() {
}

public void inject(Object target) {
this.serializationService = (SerializationService)ARouter.getInstance().navigation(SerializationService.class);
Test1Activity substitute = (Test1Activity)target;
substitute.name = substitute.getIntent().getStringExtra(“name”);
substitute.age = substitute.getIntent().getIntExtra(“age”, 0);
substitute.girl = substitute.getIntent().getBooleanExtra(“boy”, false);
substitute.pac = (TestParcelable)substitute.getIntent().getParcelableExtra(“pac”);
if(null != this.serializationService) {
substitute.obj = (TestObj)this.serializationService.json2Object(substitute.getIntent().getStringExtra(“obj”), TestObj.class);
} else {
Log.e(“ARouter::”, “You want automatic inject the field ‘obj’ in class ‘Test1Activity’ , then you should implement ‘SerializationService’ to support object auto inject!”);
}

substitute.url = substitute.getIntent().getStringExtra(“url”);
substitute.helloService = (HelloService)ARouter.getInstance().navigation(HelloService.class);
}
}

public class BlankFragment$$ARouter$$Autowired implements ISyringe {
private SerializationService serializationService;

public BlankFragment$$ARouter$$Autowired() {
}

public void inject(Object target) {
this.serializationService = (SerializationService)ARouter.getInstance().navigation(SerializationService.class);
BlankFragment substitute = (BlankFragment)target;
substitute.name = substitute.getArguments().getString(“name”);
if(null != this.serializationService) {
substitute.obj = (TestObj)this.serializationService.json2Object(substitute.getArguments().getString(“obj”), TestObj.class);
} else {
Log.e(“ARouter::”, “You want automatic inject the field ‘obj’ in class ‘BlankFragment’ , then you should implement ‘SerializationService’ to support object auto inject!”);
}

if(null == substitute.obj) {
Log.e(“ARouter::”, “The field ‘obj’ is null, in class '” + BlankFragment.class.getName() + “!”);
}

}
}

我们分析下Test1Activity A R o u t e r ARouter ARouterAutowired的代码,核心就是inject(Object target)

  • 通过Arouter路由框架的IOc的ByType方式对SerializationService进行注入,该类为Json转换的工具类
  • 注意助理使用的是ByType方式,也就是直接找到实现了SerializationService接口的唯一类进行实例化并注入,如果实现了SerializationService接口的有多个类,那么就会出现问题
  • 所以全局应用的所有模块中,只能存在一个实现了SerializationService接口的类
  • 获取目标对象实例
  • 利用目标对象的对应传值方式,对目标对象的实例中的成员变量进行赋值
  • Acitivty使用的getIntent()—–由框架自身的参数传递决定,详见 4.3API部分
  • Fragment使用getArguments()—–由框架自身的参数传递决定,详见 4.3API部分
  • OBJ对象使用JSon辅助类进行实例化转换—– 详见 4.3API部分,传递参数时会将对象封装为json字符串
  • IOc依赖注入对象,默认使用byType方式,如果Autowired注解中有标识name,则使用name指向的类实例并赋值

4.2.3 Interceptor注解处理

@Interceptor(priority = 7)
public class Test1Interceptor implements IInterceptor {

}

public class ARouter$$Interceptors$$app implements IInterceptorGroup {
public ARouter$$Interceptors$$app() {
}

public void loadInto(Map<Integer, Class<? extends IInterceptor>> interceptors) {
interceptors.put(Integer.valueOf(7), Test1Interceptor.class);
}
}

4.3 arouter-api路由控制

【图4.3.1 图4.3.2】
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  • 最上层是Launcher层,这一层是开发者可以直接用到的,其实所有的API都是在这一层中。
  • 在Launcher层的下一层就是Frossard层,从上图中可以看到Frossard层也是绿色的,表示这一层也是可以被外部调用的,Frossard层其实包含了三部分,分别是:Service、Callback和Template.
  • 这里的Service概念和服务端的Service概念是相似的,也是在客户端的简单引申,但是却不同于Android组件中的Service,这里的Service是ARouter抽象出来的概念,从本质上讲,这里的Service是接口,从意义上讲是将一定的功能和组件封装成接口,并对外提供能力。
  • Template则是模板。Compiler模块在编译期会生成一些映射文件,而这些映射文件的生成规则就是根据Template来生成的,通过记录Template的相关接口函数名称+参数等,生成加载逻辑的代码,这样按照一定的规则和约束生成映射文件也方便Route在运行的时候进行读取。
  • 再往下一层就完全是SDK的内部实现了,这一层包括了Ware House、Thread、Log、Exception以及Class工具。
  • Ware House主要存储了ARouter在运行期间加载的一些配置文件以及映射关系;
  • 而Thread则是提供了线程池,因为存在多个拦截器的时候以及跳转过程中都是需要异步执行的;
  • Class工具则是用于解决不同类型APK的兼容问题的。
  • 再下一层就是Logistics Center,从名字上翻译就是物流中心,整个SDK的流转以及内部调用最终都会下沉到这一层,当然也会按照功能模块进行划分。

4.3.1 ARouter init 初始化过程分析

经过前面的分析,我们几乎可以断定,ARouter 的初始化过程最重要的一步一定是把前面编译产生的路由清单文件加载到内存,形成一个路由表,以供后面路由查找之用。

Class ARouter 其实是一个代理类,它的所有函数实现都交给Class _ARouter去实现,两个都是单例模式

我们先来看 arouter-api 中的 com.alibaba.android.arouter.launcher.ARouter

/**

  • Init, it must be call before used router.
    */
    public static void init(Application application) {//静态函数进行初始化,不依赖对象
    if (!hasInit) {
    logger = _ARouter.logger; //持有 日志打印的 全局静态标量
    _ARouter.logger.info(Consts.TAG, “ARouter init start.”);//打印 ARouter初始化日志
    hasInit = _ARouter.init(application);//移交 _ARouter去 初始化

if (hasInit) {
_ARouter.afterInit();
}

_ARouter.logger.info(Consts.TAG, “ARouter init over.”);//打印 ARouter初始化日志
}
}

既然代码移交到了 _ARouter去 初始化,我们再来看

protected static synchronized boolean init(Application application) {
mContext = application;// Application的上下文
LogisticsCenter.init(mContext, executor);//移交逻辑中心进行初始化,并传入线城池对象
logger.info(Consts.TAG, “ARouter init success!”);//打印日志
hasInit = true;//标示是否初始化完成

// It’s not a good idea.
// if (Build.VERSION.SDK_INT > Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
// application.registerActivityLifecycleCallbacks(new AutowiredLifecycleCallback());
// }
return true;
}

继续往下走, LogisticsCenter 中进行初始化

public synchronized static void init(Context context, ThreadPoolExecutor tpe) throws HandlerException {
mContext = context; //静态持有Application的上下文
executor = tpe;//静态持有 线城池

try {
// These class was generate by arouter-compiler.
// 通过指定包名com.alibaba.android.arouter.routes,找到所有 编译期产生的routes目录下的类名(不包含装载类)
List classFileNames = ClassUtils.getFileNameByPackageName(mContext, ROUTE_ROOT_PAKCAGE);

for (String className : classFileNames) {//【组别的清单列表】com.alibaba.android.arouter.routes.ARouter$$Root
if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_ROOT)) {
// This one of root elements, load root.
((IRouteRoot) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.groupsIndex);
} else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_INTERCEPTORS)) {//【模块内的拦截器清单列表】com.alibaba.android.arouter.routes.ARouter$$Interceptors
// Load interceptorMeta
((IInterceptorGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.interceptorsIndex);
} else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_PROVIDERS)) {//【Ioc的动作路由清单列表】com.alibaba.android.arouter.routes.ARouter$$Providers
// Load providerIndex
((IProviderGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.providersIndex);
}
}

if (Warehouse.groupsIndex.size() == 0) {
logger.error(TAG, “No mapping files were found, check your configuration please!”);
}

if (ARouter.debuggable()) {
logger.debug(TAG, String.format(Locale.getDefault(), “LogisticsCenter has already been loaded, GroupIndex[%d], InterceptorIndex[%d], ProviderIndex[%d]”, Warehouse.groupsIndex.size(), Warehouse.interceptorsIndex.size(), Warehouse.providersIndex.size()));
}
} catch (Exception e) {
throw new HandlerException(TAG + “ARouter init logistics center exception! [” + e.getMessage() + “]”);
}
}

首先,我们要有一个概念。前文说了,我们断定初始化操作是要把 映射信息加载到内存,那么在哪里存储呢?这里就引入了 内存仓库Warehouse的概念,实际上它就是持有了多个statice 的Map对象,在下文我们给出其源码。

我们继续分析LogisticsCenter.init(),首先就是根据包名com.alibaba.android.arouter.routes找到了下边的所有类,那些类呢?所有模块下的这几个类:
【图4.3.1.1】
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
这些类都是编译期间通过“手动”的方式创建出来的,他们分别继承了IRouteRoot、IInterceptorGroup、IProviderGroup接口,实现了其loadInto()方法
接下来就是,根据反射将类实例化出来后,调用其loadInto()将对应的路由信息加载到 内存仓库中

需要注意的是:初始化阶段只加载了Root【组别的清单列表】,,并没有具体载入每个 Group 中包含的具体的路由节点清单,这就与 ARouter 的官方说明一致了:映射关系按组分类、多级管理,按需初始化,只有我们使用到具体的 Group 时,才会加载对应的 Group 列表。

class Warehouse {
// Cache route and metas
static Map<String, Class<? extends IRouteGroup>> groupsIndex = new HashMap<>();//【组别的清单列表】 包含了组名与对应组内的路由清单列表Class的映射关系(这里只存储了未导入到 routes在键盘每个的组)
static Map<String, RouteMeta> routes = new HashMap<>();//【组内的路由清单列表】包含了对应分组下的,路由URL与目标对象Class的映射关系;

// Cache provider
static Map<Class, IProvider> providers = new HashMap<>(); //缓存 IOC 目标class与已经创建了的对象 TODO ?全局应用共享一个IOc依赖注入对象?
static Map<String, RouteMeta> providersIndex = new HashMap<>();//【Ioc的动作路由清单列表】包含了使用依赖注入方式的某class的 路由URL 与class映射关系

// Cache interceptor
//【模块内的拦截器清单列表】包含了某个模块下的拦截器 与 优先级的映射关系
static Map<Integer, Class<? extends IInterceptor>> interceptorsIndex = new UniqueKeyTreeMap<>(“More than one interceptors use same priority [%s]”);
static List interceptors = new ArrayList<>();//已排序的拦截器实例对象

}

最后,别忘了最开始的_ARouter.afterInit(),根据 Ioc.ByName()方式获取 拦截器界面,注意这个拦截器并不是我们定义的拦截器,而是Arouter实现的拦截器逻辑,它持有我们定义的拦截器,可以理解为“拦截器截面控制器”

static void afterInit() {
// Trigger interceptor init, use byName.
interceptorService = (InterceptorService) ARouter.getInstance().build(“/arouter/service/interceptor”).navigation();
}

至此,初始化工作全部完成,其中 内存仓库Warehouse缓存了全局应用的【组别的清单列表】、【Ioc的动作路由清单列表】、【模块内的拦截器清单列表】,3个以_index为结尾的Map对象

4.3.2 ARouter 运行时 API 调用过程分析

入口:

ARouter.getInstance()
.build(“/test/activity2”)
.navigation();

单例模式我们不过多赘述,我们看build的最终调用,其使用了代理类_ARouter的build()并构建和返回PostCard对象,先简单的提一下 一个Postcard 对象就对应了一次路由请求,该对象作用于本次路由全过程

public Postcard ARouterbuild(String path) {
return _ARouter.getInstance().build(path);
}

protected Postcard _ARouter.build(String path) {
if (TextUtils.isEmpty(path)) {//如果路径为null
throw new HandlerException(Consts.TAG + “Parameter is invalid!”);
} else {
PathReplaceService pService = ARouter.getInstance().navigation(PathReplaceService.class);//通过ARouter的Ioc方式(IProvider的ByType())方式找到 动态修改路由类
if (null != pService) {
path = pService.forString(path); //如果全局应用有实现 PathReplaceService.class接口,则执行 “运行期动态修改路由”逻辑。生成转换后的路由
}
return build(path, extractGroup(path));
}
}

其实整个build的过程分为两个顺序部分:

  1. 使用 Ioc byType()方式寻找PathReplaceService.class接口的实现类,以期实现 “运行期动态修改路”
  2. 正常的本次路由导航
4.3.2.1 运行期动态修改路由PathReplaceService的实现(IOC 也就是IProvider.byType() = navigation(class)的实现方式,用于获取 路由目标实例)

在_ARouter的build()中又调用了ARouter的T navigation(Class

protected T _ARouter.navigation(Class<? extends T> service) {
try {
Postcard postcard = LogisticsCenter.buildProvider(service.getName());

// Compatible 1.0.5 compiler sdk.
if (null == postcard) { // No service, or this service in old version.
postcard = LogisticsCenter.buildProvider(service.getSimpleName());
}

LogisticsCenter.completion(postcard);
return (T) postcard.getProvider();
} catch (NoRouteFoundException ex) {
logger.warning(Consts.TAG, ex.getMessage());
return null;
}
}

//1. 从内存仓库的【Ioc的动作路由清单列表】中找到,对应Name对应的 路由元信息
//2. 根据路由元信息 生成 Postcard对象,赋值其 路径URL 和 组名 信息
public static Postcard LogisticsCenter.buildProvider(String serviceName) {
RouteMeta meta = Warehouse.providersIndex.get(serviceName);

if (null == meta) {
return null;
} else {
return new Postcard(meta.getPath(), meta.getGroup());
}
}

找到Postcard后,执行 LogisticsCenter.completion(postcard)函数,这个函数是一个至关重要的函数,其在于完善postcard对象,而后调用其getProvider()获取目标对象实例。具体的细节我们在4.2.2.2中讲解

4.3.2.2 一次简单的路由导航

入口:_ARouter.build(path, extractGroup(path)), 其中 _ARouter.extractGroup(string)从路径中找到,默认的 分组,也就是 一级路径,例如”/11/22”—11 可以看到,该函数也执行了一次 “4.3.2.1 运行期动态修改路由”逻辑,这是由于,该函数可以被单独调用所必须要求的

最后

如果你看到了这里,觉得文章写得不错就给个赞呗?如果你觉得那里值得改进的,请给我留言。一定会认真查询,修正不足。谢谢。

最后针对Android程序员,我这边给大家整理了一些资料,包括不限于高级UI、性能优化、移动架构师、NDK、混合式开发(ReactNative+Weex)微信小程序、Flutter等全方面的Android进阶实践技术;希望能帮助到大家,也节省大家在网上搜索资料的时间来学习,也可以分享动态给身边好友一起学习!
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》点击传送门,即可获取!
me);

if (null == meta) {
return null;
} else {
return new Postcard(meta.getPath(), meta.getGroup());
}
}

找到Postcard后,执行 LogisticsCenter.completion(postcard)函数,这个函数是一个至关重要的函数,其在于完善postcard对象,而后调用其getProvider()获取目标对象实例。具体的细节我们在4.2.2.2中讲解

4.3.2.2 一次简单的路由导航

入口:_ARouter.build(path, extractGroup(path)), 其中 _ARouter.extractGroup(string)从路径中找到,默认的 分组,也就是 一级路径,例如”/11/22”—11 可以看到,该函数也执行了一次 “4.3.2.1 运行期动态修改路由”逻辑,这是由于,该函数可以被单独调用所必须要求的

最后

如果你看到了这里,觉得文章写得不错就给个赞呗?如果你觉得那里值得改进的,请给我留言。一定会认真查询,修正不足。谢谢。

[外链图片转存中…(img-yDmK9fxj-1715424051729)]

最后针对Android程序员,我这边给大家整理了一些资料,包括不限于高级UI、性能优化、移动架构师、NDK、混合式开发(ReactNative+Weex)微信小程序、Flutter等全方面的Android进阶实践技术;希望能帮助到大家,也节省大家在网上搜索资料的时间来学习,也可以分享动态给身边好友一起学习!
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》点击传送门,即可获取!

  • 16
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值