ARouter解析之路由的秘密

ARouter解析

简介

本篇主要介绍ARouter如何进行路由的,比如Activity、Fragment、服务等

路由的秘密

基本使用

初始化路由之后

1
ARouter.init(mApplication);

在需要支持页面路由的页面上添加注解(至少两级)

1
2
@Route(path = "/test/activity2")
public class Test2Activity extends AppCompatActivity

在路由时候通过建造者模式构建参数,路由页面分为以下几种

路由页面

  • 普通跳转
1
2
3
ARouter.getInstance()
.build("/test/activity2")
.navigation();
  • 带参数跳转
1
2
3
4
ARouter.getInstance()
.build("/test/activity2")
.withString("key1", "value1")
.navigation();

其中ARouter提供了丰富的参数类型主要有基本类型、Object、Parceable等,核心原理也是通过Bundle携带传递

  • startActivityForResult

比如在TestActivity中

1
2
3
ARouter.getInstance().build("/test/activity2")
.withString("key1", "value1")
.navigation(this, 999);

然后在TestActivity中 使用onActivityResult得到数据进行处理,这里跟普通过程一样

  • 带回调路由
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
ARouter.getInstance().build("/xxx/xxx").navigation(this, new NavCallback() {
@Override
public void onFound(Postcard postcard) {
Log.d("ARouter", "找到了");
}

@Override
public void onLost(Postcard postcard) {
Log.d("ARouter", "找不到了");
}

@Override
public void onArrival(Postcard postcard) {
Log.d("ARouter", "跳转完了");
}

@Override
public void onInterrupt(Postcard postcard) {
Log.d("ARouter", "被拦截了");
}
});

路由Fragment

1
2
3
Fragment fragment = (Fragment) ARouter.getInstance()
.build("/test/fragment")
.navigation();

路由服务

1
2
3
ARouter.getInstance()
.navigation(SingleService.class)
.sayHello("Mike");

源码分析

可以看出路由到页面 分为三步

  • build 构建 信息邮票(PostCard)

_ARouter # build

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* 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));
}
}
  • WithXXX 路由参数

  • navagation 导航

_ARouter # navagation

1
2
3
public Object navigation(Context mContext, Postcard postcard, int requestCode, NavigationCallback callback) {
return _ARouter.getInstance().navigation(mContext, postcard, requestCode, callback);
}

下面将按照上面的提到三个核心过程进行分析

  • 通过path构建路由信息

build是在构建路由信息邮票”PostCard”,从名字看来这就是一张路由邮票,跟网络路由协议传递携带的信息作用一样,通过ARouter这个门面类(Facade)调用实际操控着_ARouter

1
2
3
4
5
6
7
8
9
10
11
12
/**
* Build postcard by path and default group
*/
protected Postcard build(String path) {
……
//路径扩展
PathReplaceService pService = ARouter.getInstance().navigation(PathReplaceService.class);
if (null != pService) {
path = pService.forString(path);
}
return build(path, extractGroup(path));
}

这里也体现了作者设计这套框架的扩展性能,这个PathReplaceService就是让使用者自己可以给path添加扩展。extractGroup就是根据path(/test/activity2)提取group(test),这里就是默认第一个/隔断的字符串,通过这些信息初步构造路由信息,

1
2
3
4
 protected Postcard build(String path, String group) {
……
return new Postcard(path, group);
}

这样一个具有 pathgroup简单信息的PostCard对象就被构造出来了

  • 携带参数

由于是Builder模式,此时构造出postcard之后之后的.WithXXX实际上实在给PostCard对象填充信息,PostCard对象的Bundle来承载这些信息,
其中 序列化对象SerializationService转成json存到Bundle中
通过这一步PostCard这个邮票信息进一步丰满了

  • navigation 导航

得到PostCard对象之后回去调用postCard对象的navigation对象,调用函数链如下

1
2
PostCard(Arouter.getInstance.build("test/activity2"))——>PostCard#navigation——>ARouter#navigation
——>_ARouter#navigation`

_ARouter#navigation

来到了核心函数 _ARouter#navigation函数中,这个函数其实是核心路由逻辑的第一道大门,我们越来越接近真相了

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
39
40
41
42
43
44
45
46
47
48
protected Object navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
try {
//完善postCard 核心信息,主要是跳转路径 后面会详细分析
LogisticsCenter.completion(postcard);
} catch (NoRouteFoundException ex) {
// 未找到页面的降级处理策略
if (null != callback) {
//自定义回调高于系统
callback.onLost(postcard);
} else { // No callback for this invoke, then we use the global degrade service.
//没有自定义回调才使用系统onLost
DegradeService degradeService = ARouter.getInstance().navigation(DegradeService.class);
if (null != degradeService) {
degradeService.onLost(context, postcard);
}
}

return null;
}

if (null != callback) {
//onFound回调
callback.onFound(postcard);
}

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);
}

@Override
public void onInterrupt(Throwable exception) {
if (null != callback) {
callback.onInterrupt(postcard);
}
}
});
} else {
//真正核心导航逻辑
return _navigation(context, postcard, requestCode, callback);
}

return null;
}

这个方法较长 主要分为以下四步

  1. 首先调用LogisticsCenter.completion完成postcard的补充
  2. NavigationCallBack的处理
  3. 拦截器Interceptions的处理
  4. 开始路由导航

路由导航之前如何将之前获取的postcard信息进行完善?
ARouter.getInstance() .build(“/test/activity2”)时已经返回了一个postcard对象,那么要完善那些信息呢?其实我们可以看出,postcard中只有path和group的信息,目标页面是什么还不明确,因此需要进一步完善信息,核心函数就是上面的LogisticsCenter.completion,这样就体现了框架的重要性,脏活累活交给框架层面,而跟用户打交道的永远都是那么的简洁。

_ARouter#navigationObject navigation(

最后会来到_ARouter#navigationObject navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback))

这个函数负责最终的页面跳转

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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
private Object _navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
final Context currentContext = null == context ? mContext : context;

switch (postcard.getType()) {
case ACTIVITY:
// Build intent
final Intent intent = new Intent(currentContext, postcard.getDestination());
intent.putExtras(postcard.getExtras());

// Set flags.
int flags = postcard.getFlags();
if (-1 != flags) {
intent.setFlags(flags);
} else if (!(currentContext instanceof Activity)) { // Non activity, need less one flag.
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
}

// Navigation in main looper.
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
if (requestCode > 0) { // Need start for result
ActivityCompat.startActivityForResult((Activity) currentContext, intent, requestCode, postcard.getOptionsBundle());
} else {
ActivityCompat.startActivity(currentContext, intent, postcard.getOptionsBundle());
}

if ((-1 != postcard.getEnterAnim() && -1 != postcard.getExitAnim()) && currentContext instanceof Activity) { // Old version.
((Activity) currentContext).overridePendingTransition(postcard.getEnterAnim(), postcard.getExitAnim());
}

if (null != callback) { // Navigation over.
callback.onArrival(postcard);
}
}
});

break;
case PROVIDER:
return postcard.getProvider();
case BOARDCAST:
case CONTENT_PROVIDER:
case FRAGMENT:
Class fragmentMeta = postcard.getDestination();
try {
Object instance = fragmentMeta.getConstructor().newInstance();
if (instance instanceof Fragment) {
((Fragment) instance).setArguments(postcard.getExtras());
} else if (instance instanceof android.support.v4.app.Fragment) {
((android.support.v4.app.Fragment) instance).setArguments(postcard.getExtras());
}

return instance;
} catch (Exception ex) {
logger.error(Consts.TAG, "Fetch fragment instance error, " + TextUtils.formatStackTrace(ex.getStackTrace()));
}
case METHOD:
case SERVICE:
default:
return null;
}

return null;
}
  • Activity

来到ACTIVITY分支,从postcard中拿到目标页面TestActivity.class然后组成intent,然后putExtras,如果是startActivityForResult,这里面就有参数。如果context不是activity,那么就需要另起一个栈Intent.FLAG_ACTIVITY_NEW_TASK进行activity的展示。接下来通过handler发送启动activity的任务。终于找到了熟悉的ActivityCompat.startActivity和ActivityCompat.startActivityForResult,

  • Fragment

通过postcard拿到目标页面的Fragment Class,然后实例化这个,还需要兼容fragment,设置Arguments参数之后返回这个Fragment实例

  • Provider

这个是用来提供服务的,由于在 完善postCard 核心信息,LogisticsCenter.completion(postcard);中已经将provider实例化了,这里直接直接get出来就好。后面将会分析LogisticsCenter.completion(postcard)具体做了哪些工作

LogisticsCenter.completion(postcard)

这个函数就是完善PostCard信息的,postcard是路由信息的容器,到这个函数时候postcard中只有path和group的信息,目标页面是什么还不知道,LogisticsCenter.completion就是填充剩余的关键信息的,b并且是通过按需加载的,十分重要

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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
public synchronized static void completion(Postcard postcard) {
……
//从仓库WareHouse中获取路由元数据,即一些路由基本的数据
RouteMeta routeMeta = Warehouse.routes.get(postcard.getPath());
if (null == routeMeta) { // Maybe its does't exist, or didn't load.
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 {
// Load route and cache it into memory, then delete from metas.
try {
……
//将信息加载进Warehouse.routes中
IRouteGroup iGroupInstance = groupMeta.getConstructor().newInstance();
iGroupInstance.loadInto(Warehouse.routes);
Warehouse.groupsIndex.remove(postcard.getGroup());
……
}
……
//经过上面的处理后,此时可以获取到对应path下的routeMeta,调用本身
completion(postcard); // Reload
}
} else {
//正常获取到routeMeta填充postCard信息
postcard.setDestination(routeMeta.getDestination());
postcard.setType(routeMeta.getType());
postcard.setPriority(routeMeta.getPriority());
postcard.setExtra(routeMeta.getExtra());

Uri rawUri = postcard.getUri();
if (null != rawUri) { // Try to set params into bundle.
Map<String, String> resultMap = TextUtils.splitQueryParameters(rawUri);
Map<String, Integer> paramsType = routeMeta.getParamsType();

if (MapUtils.isNotEmpty(paramsType)) {
// Set value by its type, just for params which annotation by @Param
for (Map.Entry<String, Integer> params : paramsType.entrySet()) {
setValue(postcard,
params.getValue(),
params.getKey(),
resultMap.get(params.getKey()));
}

// Save params name which need auto inject.
postcard.getExtras().putStringArray(ARouter.AUTO_INJECT, paramsType.keySet().toArray(new String[]{}));
}

// Save raw uri
postcard.withString(ARouter.RAW_URI, rawUri.toString());
}

switch (routeMeta.getType()) {
//完成信息的同时顺便把"服务实例化",fragment置为无须过滤的绿色通道
case PROVIDER: // if the route is provider, should find its instance
// Its provider, so it must implement IProvider
Class<? extends IProvider> providerMeta = (Class<? extends IProvider>) routeMeta.getDestination();
IProvider instance = Warehouse.providers.get(providerMeta);
if (null == instance) { // There's no instance of this provider
IProvider provider;
try {
provider = providerMeta.getConstructor().newInstance();
provider.init(mContext);
Warehouse.providers.put(providerMeta, provider);
instance = provider;
} catch (Exception e) {
throw new HandlerException("Init provider failed! " + e.getMessage());
}
}
postcard.setProvider(instance);
postcard.greenChannel(); // Provider should skip all of interceptors
break;
case FRAGMENT:
postcard.greenChannel(); // Fragment needn't interceptors
default:
break;
}
}
}

流程图如下
arouter-complete

这个有一个关键点 WearHouse

  • 仓库查找页面节点

首先根据路径信息到WareHouse仓库中查找路由节点信息,其实就是几个map,包含节点、拦截器和组别等信息

1
RouteMeta routeMeta = Warehouse.routes.get(postcard.getPath());

一开始是没有这个节点信息的,所以需要到WareHouse.groupsIndex中找到组别的信息,这里体现了之前提到的分组加载的策略,这里对应的是test这一组

1
Class<? extends IRouteGroup> groupMeta = Warehouse.groupsIndex.get(postcard.getGroup());  // Load route meta

然后通过反射的方式加载这一组类别的映射关系,就是前面提到的按需加载,然后从仓库中删除这个组别信息节点,防止重复加载

IRouteGroup iGroupInstance = groupMeta.getConstructor().newInstance();        
iGroupInstance.loadInto(Warehouse.routes);    
Warehouse.groupsIndex.remove(postcard.getGroup());

其中编译期间已经组成了RouteMeta这个节点信息,包含有目标页面、类型、路径、组别、参数、优先级等信息。可以看到生成之后的信息表并没有直接加载到内存中,而是在第一次访问该组内一个页面时才去加载该组的信息,然后该groupindex移除,防止重复加载

生成的ARouter$$Group$$test

test组

  • 填充信息

当第一次没有查找到路由节点之后,要到组别中找到路由信息 load到warehouse中,再次调用本身completion(postcard)函数.然后执行到else中填充路由信息

  • 一些初始化

填充玩信息后,将”服务”实例化,方便后面直接获取,并且将“服务”和fragment设置为绿色通道无需检查,感觉是由于二者本身需要返回一个实例,回调接口NavCallBack只能回调PostCard信息,而且二者近似服务性质,因而不如直接给个绿色通道来的直接。

小结

路由之间跳转到的分析到此基本上告一段落了,其中众多信息都在PostCard中不断完善,然后在LogisticsCenter.completion进行完全填充,更重要的是整体架构关注点分离的设计是非常棒的,编译期间映射信息都下沉到LogisticsCenter与用户层打交道的API都通过ARouter这个门面友好的暴露给用户,我们平时在开发设计中也可以这样学习一下

0%