ARouter源码浅析

这篇文章主要分析ARouter中路由与服务相关的源码(不涉及依赖注入)。

路由框架要解决哪些问题?

在开始源码分析之前,先考虑一下这个问题。

在这一节,会抛出几个问题,而下面的源码分析,就会围绕这一节的问题而展开。

1、路由信息如何搜集?

2、如何完成跳转,如何传参?

3、跳转过程中需要插入一些公用逻辑怎么办?

4、跳转过程中出现异常或者跳转失败怎么办?

框架整体设计

在开始具体的代码流程分析之前,可以先大致了解下框架的整体设计。简单整理下主要的类与模块,可以得到下面的结构图:

image-20180912205308489

简单介绍下每个类或模块的大概职责。

ARouter

外部调用的最主要功能接口,提供路由跳转的入口。

IProvider

服务提供者的抽象。

annotation

因为ARouter的基本实现是基于APT的,所以需要通过annotation暴露路由注册功能。

_Arouter

路由跳转的具体实现。

LogisticsCenter

负责路由数据的初始化以及管理。

APT

用于生成路由表(路由所需的基本信息)

路由注册

搜集路由信息使用了APT技术,相关细节这里不再讨论,我们主要关注一下APT的生成类,看下主要搜集了哪些信息。

对于Activity

1
2
3
4
5
6
7
@Route(path = "/com/activity/ResultActivity")
class ResultActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_result)
}
}

会生成对应的路由信息类

1
2
3
4
5
6
7
8
/**
* DO NOT EDIT THIS FILE!!! IT WAS GENERATED BY AROUTER. */
public class ARouter$$Group$$com implements IRouteGroup {
@Override
public void loadInto(Map<String, RouteMeta> atlas) {
atlas.put("/com/activity/ResultActivity", RouteMeta.build(RouteType.ACTIVITY, ResultActivity.class, "/com/activity/resultactivity", "com", null, -1, -2147483648));
}
}

可以发现搜集的信息主要为路径字符串与路由目标的映射,以及一些路由元数据RouteMeta。

初始化

初始化以ARouter.init()为入口,最后进入到LogisticsCenter.init()。

初始化的过程可以通过插件完成,我们这里只关注不通过插件完成的逻辑,提取我们关心的核心逻辑,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// 这里的代码是经过修改与裁剪的,仅为展示逻辑用,与源代码有一定差距
public synchronized static void init(Context context, ThreadPoolExecutor tpe) {
// 存储APT生成的路由信息类的类名
Set<String> routerMap;
if (PackageUtils.isNewVersion(context)) {
// 通过包名查找路由信息类
routerMap = ClassUtils.getFileNameByPackageName(packageName);
if (!routerMap.isEmpty()) {
// 在这里将routerMap持久化
}
// Save new version name when router map update finishes.
PackageUtils.updateVersion(context);
} else {
// 如果app没有更新,则说明路由表没变,则从持久化缓存中取出路由信息
routerMap = getRouterMapFromCache();
}

// 根据路由信息类的不同类型将其分类存储于Warehouse中
for (String className : routerMap) {
if (isRoot(className)) {
Class.forName(className)
.getConstructor()
.newInstance()
.loadInto(Warehouse.groupsIndex);
} else if (isInterceptor(className)) {
// 注册拦截器
Class.forName(className)
.getConstructor()
.newInstance()
.loadInto(Warehouse.interceptorsIndex);
} else if (isProvider(className)) {
Class.forName(className)
.getConstructor()
.newInstance()
.loadInto(Warehouse.providersIndex);
}
}
}

可以发现初始化的主要工作就是将APT收集的类信息加载到内存中。

除此之外,_ARouter.afterInit()会在初始化完成后被调用,用以初始化拦截器服务,其具体的功能后面会介绍。

路由跳转

路由跳转最终会进入到_ARouter的navigation(),具体过程可以分解为路由查找与路由跳转两步骤,接下来分析路由跳转的具体过程。

参数传递

参数传递主要通过类Postcard,由它来接收并传递路由跳转过程中需要的参数。换个角度想,要完成路由的功能,我们所需要的信息包含两部分,一部分是路由的目标相关的信息,这些信息在我们开始写业务代码之前就可以预先知道,因此可以通过APT生成,而另外一部分,比如要传给目标路由的参数,这个则是与具体的业务有关的,因此需要我们在业务代码中完成信息传递,而这部分信息的承载者,就是Postcard。具体的细节直接去看Postcard的类结构就行了,这里不再赘述。

路由查找

路由跳转的第一步就是先查找到目标路由,路由查找的具体实现在LogisticsCenter.completion(postcard)中,其核心逻辑可以概括为,从路由信息仓库Warehouse中查找路由信息,然后缓存,便于下次快速访问。其代码可以精简如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public synchronized static void completion(Postcard postcard) {
// Warehouse.routes是用于快速访问的缓存
RouteMeta routeMeta = Warehouse.routes.get(postcard.getPath());
if (null == routeMeta) {
// 从Warehouse中读取路由信息并缓存
Class<IRouteGroup> groupMeta = Warehouse.groupsIndex.get(postcard.getGroup());
IRouteGroup iGroupInstance = groupMeta.getConstructor().newInstance();
iGroupInstance.loadInto(Warehouse.routes);
Warehouse.groupsIndex.remove(postcard.getGroup());
} else {
// 将路由信息类中的路由信息也一并整理到postcard中
postcard.setDestination(routeMeta.getDestination());
postcard.setType(routeMeta.getType());
postcard.setPriority(routeMeta.getPriority());
postcard.setExtra(routeMeta.getExtra());
// 因为支持uri,所以这里还有从uri中解析出查询语句,并将其传入postcard的逻辑
}

// Service是单例,在这里进行查找或实例化(个人觉得这个逻辑放在路由跳转中会更统一一点)
switch (routeMeta.getType()) {
case PROVIDER:
// 实例化Service并将其缓存在Warehouse.providers中
break;
}
}
路由跳转

跳转的最终实现在_navigation()中,该方法的实现可以概括为,对于Activity,跳转意味着跳转到该Activity;对于Service来说,跳转意味着返回之前实例化的单例;对于Fragment来说,意味着实例化Fragment并返回。代码精简如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
private Object _navigation() {
switch (postcard.getType()) {
case ACTIVITY:
// 先将postcard中的信息取出,包括动画、flag等信息,一并设置到intent中
startActivity();
break;
case PROVIDER:
return postcard.getProvider();
case FRAGMENT:
Object instance = fragmentMeta.getConstructor().newInstance();
((Fragment) instance).setArguments(postcard.getExtras());
return instance;
}
}

作者这里还预留了其他类型,比如方法、CONTENT_PROVIDER等未实现。不过不论如何,这里的逻辑都是获取路由信息,然后跳转并传递。

拦截器

注册与查找拦截器

拦截器是Provider的一种,因此其注册方式也是与其他的Provider一样,都是通过APT生成路由表。

在初始化那一节中提到,_ARouter.afterInit()会在初始化完成后被调用,用以初始化拦截器服务。而这个拦截器服务InterceptorServiceImpl初始化的时候,则会完成拦截器的查找与缓存。具体的逻辑在InterceptorServiceImpl的init()方法中:

1
2
3
4
5
6
7
8
9
10
public void init(final Context context) {
for (Map.Entry<Integer, Class<? extends IInterceptor>> entry : Warehouse.interceptorsIndex.entrySet()) {
Class<? extends IInterceptor> interceptorClass = entry.getValue();
IInterceptor iInterceptor = interceptorClass.getConstructor().newInstance();
// 每个拦截器自己还有自己的初始化逻辑
iInterceptor.init(context);
// 将拦截器单例缓存到Warehouse.interceptors中
Warehouse.interceptors.add(iInterceptor);
}
}
调用拦截器

InterceptorServiceImpl本质上就是拦截器的管理器,因此它不仅负责拦截器的注册查找,还负责拦截器的调用。

拦截器的调用发生在LogisticsCenter.completion(postcard)与Arouter._navigation()之间,也就是发生在路由查找与路由跳转之间。

其具体实现都在InterceptorServiceImpl的doInterceptions()方法中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public void doInterceptions(final Postcard postcard, final InterceptorCallback callback) {
LogisticsCenter.executor.execute(new Runnable(){
@Override
public void run() {
CancelableCountDownLatch interceptorCounter = new CancelableCountDownLatch(Warehouse.interceptors.size());
try {
_excute(0, interceptorCounter, postcard);
interceptorCounter.await(postcard.getTimeout(), TimeUnit.SECONDS);
if (interceptorCounter.getCount() > 0) {
callback.onInterrupt(new HandlerException());
}
} catch (Exception e) {
callback.onInterrupt(e);
}
}
})
}

private static void _excute(final int index, final CancelableCountDownLatch counter, final Postcard postcard) {
if (index < Warehouse.interceptors.size()) {
IInterceptor iInterceptor = Warehouse.interceptors.get(index);
iInterceptor.process(postcard, new InterceptorCallback() {
@Override
public void onContinue(Postcard postcard) {
counter.countDown();
_excute(index + 1, counter, postcard);
}

@Override
public void onInterrupt(Throwable exception) {
counter.cancel();
}
});
}
}

拦截的主要步骤为依次调用所有拦截器的process()方法,并且使用一个CountDownLatch来计数,然后在onContinue()方法中使计数器-1。假如是在其他线程中执行的拦截,那么拦截器Server的主线程会阻塞,直到所有的拦截器都执行完毕。需要注意的是,CountDownLatch设置了一个超时时间,也就意味着如果异步(与拦截器Server异步)的拦截器没有在限定时间内执行完,那么最外层就回回调超时异常,导致跳转失败。虽然跳转失败了,但是只要拦截器仍然调用了onContinue(),那后续的拦截器逻辑都会走一遍,只不过最后不会再回调最外层的onContinue()。

降级策略

降级策略指的是在路由失败后的客户端的行为。

降级入口

从降级的定义来看,也可以猜到降级的入口应该在路由查找这里。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
protected Object navigation() {
try {
LogisticsCenter.completion(postcard);
} catch (NoRouteFoundException ex) {
if (null != callback) {
callback.onLost(postcard);
} else {
DegradeService degradeService = ARouter.getInstance().navigation(DegradeService.class);
if (null != degradeService) {
degradeService.onLost(context, postcard);
}
}
return null;
}
}

从这里也可以看出降级有两个策略,一个是通过回调单独降级,一个是通过Service的形式进行全局降级,并且单独降级的优先级高于全局降级。

单独降级

NavigationCallback的onLost()方法可以实现单独降级。

全局降级

DegradeService 则被用来实现全局降级。全局降级的注册方式和上面的其他服务是一样的,但是由于Warehouse.providersIndex这个map的key是服务接口的全限定名,因此我们只能实现一个降级处理器,多实现的处理器会被覆盖掉。具体的细节可以参考APT生成的Providers路由表类。

参考资料

https://github.com/alibaba/ARouter