来看一个更有趣的 Router

在前一篇博文中,已经分析了阿里开源的 ARouter 框架,这篇文章将对另一款更加灵活、简单的路由框架 Router 进行分析,看看它和 ARouter 比起来有什么不同。

容易的用法

和往常一样,分析一款框架还是先从回顾用法开始,读者可以点这里去 Github 看看 Router 的介绍,不然可能一脸懵逼地不知道我在讲什么。

初始化流程稍显复杂,需要指定我们的应用中有哪些模块:

1
2
3
4
5
6
Router.initialize(new Configuration.Builder()
// 调试模式
.setDebuggable(BuildConfig.DEBUG)
// 指定所有module
.registerModules("module1", "module2", "app")
.build());

然后看看多样的跳转方式,首先,一样用 @Route 注解标记需要路由的组件,值得注意的是:

  1. 不需要指定组,但是要完整的 URI 格式
  2. 可以为一个组件指定多个路径,可以是名字,也可以是 URI 格式
1
2
3
4
@Route({"module1", "router://filter/module1"})
public class Module1Activity extends AppCompatActivity {
...
}

在 Activity 或 Fragment 或 Context 下,都可以携带路径参数调用方法实现跳转:

1
2
3
4
5
6
// 可以指定命名式路径
Router.build("module1").go(this);
// 也可以指定URI式路径
Router.build("router://filter/module1").go(this);
// 甚至隐式Intent
Router.build("router://implicit").go(this);

这里提一下,隐式 Intent 方式,不需要在组件注解,直接在 Manifest 中指定如下即可:

1
2
3
4
5
6
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.xxxx.DEFAULT" />
<category android:name="android.xxxx.BROWSABLE" />
<data android:host="implicit" android:scheme="router" />
</intent-filter>

可见,跳转方式很灵活,接下来还有拦截器,较于 ARouter 而言,Router 既支持配置全局拦截器,也支持对特定组件单独配置拦截器:

1
2
3
4
5
6
7
8
// 全局拦截器
Router.addGlobalInterceptor(new MyInterceptor());

// 对特定组件配置拦截器
@Route(value = "intercepted", interceptors = "SampleInterceptor")
public class InterceptedActivity extends AppCompatActivity {
...
}

不仅如此,Router 还将路由匹配相关逻辑进行了抽象,可以实现开发自定义规则的匹配器,甚至动态注册路由表:

1
2
3
4
5
6
7
// 动态注册路由表
Router.handleRouteTable(new RouteTable() {
@Override
public void handle(Map<String, Class<?>> map) {
map.put("dynamic", DynamicActivity.class);
}
});

以上便是 Router 的常见用法,更强的 API 赋予更佳的灵活性,接下来看看这些灵活性是如何实现的。

不见怪的路由表

同样,路由表也是通过 APT 技术在编译前期生成的,不同于 ARouterRouter 中没有分组的概念,即不会生成组管理类,取而代之的是直接生成各个模块对应的模块内路径关联类,如「app」模块的路由表:

WX20180330-111137@2x.png

同时,Router 更专注于路由,没有「Provider」一类的提供者服务,也无需生成对应的类。但是,Router 的拦截器支持对单一组件配置拦截器,所以拦截器和组件的对应关系需要生成类:

WX20180330-111702@2x.png

为实现拦截器的动态组件化,拦截器的名字也得独立开来,少不了拦截器名和类的关联:

WX20180330-111903@2x.png

最后,参数注入功能和 ARouter 没区别,都是生成相应的注入类:

WX20180330-112144@2x.png

Router 中生成类的类名都是按规则能够事先确定的,使得运行期加载时直接根据确定的类名进行类加载,而非像 ARouter 一样找出包下所有的类,然后筛选进行加载。生成类分析为止,接下来看运行期的分析。

一样的初始化

初始化的思路大致一致,实例化生成类然后进行缓存,不同的是加载不支持 Gradle 插件方式加载,也无需找出包中的类进行筛选,而是根据开发者提供注册的模块,Router 直接确定类名反射实例化,来看看:

1
2
3
4
5
6
7
8
// class Router

public static void initialize(Configuration configuration) {
// 控制日志开关
RLog.showLog(configuration.debuggable);
// 加载路由信息到AptHub
AptHub.registerModules(configuration.modules);
}

根据提供的模块名,AptHub 中将路由信息进行加载:

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
// class AptHub

synchronized static void registerModules(String... modules) {
if (modules == null || modules.length == 0) {
RLog.w("empty modules.");
} else {
// 分割符号替换为'_'
validateModuleName(modules);
// 按模块,加载路由图信息到routeTable
String routeTableName;
for (String module : modules) {
try {
// com.chenenyu.router.Module1RouteTable
// 反射实例化生成类
routeTableName = PACKAGE_NAME + DOT + capitalize(module) + ROUTE_TABLE;
Class<?> routeTableClz = Class.forName(routeTableName);
Constructor constructor = routeTableClz.getConstructor();
RouteTable instance = (RouteTable) constructor.newInstance();
// 加入路由信息到routeTable
instance.handle(routeTable);
} catch (ClassNotFoundException e) {
...
}
}

// 同理,加载组件和拦截器的关联信息
String targetInterceptorsName;
for (String moduleName : modules) {
try {
// com.chenenyu.router.Module1TargetInterceptors
targetInterceptorsName = PACKAGE_NAME + DOT + capitalize(moduleName) + TARGET_INTERCEPTORS;
Class<?> clz = Class.forName(targetInterceptorsName);
Constructor constructor = clz.getConstructor();
TargetInterceptors instance = (TargetInterceptors) constructor.newInstance();
instance.handle(targetInterceptors);
} catch (ClassNotFoundException e) {
...
}
}

// 加载所有拦截器
String interceptorName;
for (String moduleName : modules) {
try {
// com.chenenyu.router.Module1InterceptorTable
interceptorName = PACKAGE_NAME + DOT + capitalize(moduleName) + INTERCEPTOR_TABLE;
Class<?> clz = Class.forName(interceptorName);
Constructor constructor = clz.getConstructor();
InterceptorTable instance = (InterceptorTable) constructor.newInstance();
instance.handle(interceptorTable);
} catch (ClassNotFoundException e) {
...
}
}
...
}
}

提供模块名后的加载过程变得很简单,可以用确定的类名直接反射加载并实例化,然后将信息存放在 AptHub 中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class AptHub {
...

// path 和 Activity/Fragment组件的关联
static Map<String, Class<?>> routeTable = ...;
// Activity/Fragment组件 和 拦截器名字的关联
static Map<Class<?>, String[]> targetInterceptors = ...;
// 拦截器名字 和 拦截器的关联
static Map<String, Class<? extends RouteInterceptor>> interceptorTable = ...;
// 参数注入名 和 参数注入器的关联
static Map<String, Class<ParamInjector>> injectors = ....;

...
}

初始化流程到此结束,得到的是路径、组件、拦截器之间的关联关系,接下来看跳转流程。

强大的跳转

跳转也是类似的 API,先构造路由参数,然后执行跳转:

1
2
3
4
5
6
7
8
9
// class Router

public static IRouter build(String path) {
return build(path == null ? null : Uri.parse(path));
}

public static IRouter build(Uri uri) {
return RealRouter.getInstance().build(uri);
}

ARouter 构造出的是一个路由请求 Postcard,跳转的逻辑经过 Postcard 后分离在 _ARouter 中;而这里构造出的是一个 RealRouter,内部封装一个路由请求 RouteRequest,跳转的逻辑就在 RealRouter 中进行实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
abstract class AbsRouter implements IRouter {
// 路由请求
RouteRequest mRouteRequest;

@Override
public IRouter build(Uri uri) {
// new路由请求
mRouteRequest = new RouteRequest(uri);
// 填充原始Uri参数
Bundle bundle = new Bundle();
bundle.putString(Router.RAW_URI, uri == null ? null : uri.toString());
mRouteRequest.setExtras(bundle);
return this;
}

...
}

其中路由请求 RouteRequest 类中包含路由所需要的所有信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class RouteRequest implements Serializable {

private Uri uri; // 目标uri
private Bundle extras; // 携带参数
private int flags; // Intent标记
private Uri data; // 隐式Intent的data
private String type; // 隐式Intent的data type
private String action; // 隐式Intent的action
private boolean skipInterceptors; // 跳过拦截器
private Set<String> removedInterceptors; // 动态移除拦截器
private Set<String> addedInterceptors; // 动态增加拦截器
private RouteCallback callback; // 回调
private int requestCode = INVALID_CODE; // 请求码
private int enterAnim = INVALID_CODE; // 进入动画
private int exitAnim = INVALID_CODE; // 退出动画
private ActivityOptionsCompat options; // Activity配置

...
}

RouteRequest 构造后,就可以调用 go() 方法进行跳转,接下来进入 go() 方法:

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
@Override
public void go(Context context) {
// 构造Intent
Intent intent = getIntent(context);
if (intent == null) {
return;
}
Bundle options = mRouteRequest.getActivityOptionsCompat() == null ? null : mRouteRequest.getActivityOptionsCompat().toBundle();

// 启动Activity
if (context instanceof Activity) {
// Activity内启动才可以request code
ActivityCompat.startActivityForResult((Activity) context, intent, mRouteRequest.getRequestCode(), options);

if (mRouteRequest.getEnterAnim() >= 0 && mRouteRequest.getExitAnim() >= 0) {
// 添加进入和退出动画
((Activity) context).overridePendingTransition(
mRouteRequest.getEnterAnim(), mRouteRequest.getExitAnim());
}
} else {
// 非Activity的Context启动
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
context.startActivity(intent, options);
} else {
context.startActivity(intent);
}
}

callback(RouteResult.SUCCEED, null);
}

思路简单,依据 RouteRequest 构造 Intent,然后携带合适的配置 startActivity 即可。重点在于 Intent 的构造,它是如何既支持显式 Intent 又支持隐式 Intent 的呢?继续进入 getIntent() 方法:

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
// class RealRouter

@Override
public Intent getIntent(Context context) {
...

if (!mRouteRequest.isSkipInterceptors()) {
// 全局拦截器,按序执行,非递归
for (RouteInterceptor interceptor : Router.getGlobalInterceptors()) {
if (interceptor.intercept(context, mRouteRequest)) {
...
return null;
}
}
}

// 取出所有注册的Matcher
List<AbsMatcher> matcherList = MatcherRegistry.getMatcher();
// 没有Matcher
if (matcherList.isEmpty()) {
callback(RouteResult.FAILED, "The MatcherRegistry contains no matcher.");
return null;
}

// 路由表
Set<Map.Entry<String, Class<?>>> entries = AptHub.routeTable.entrySet();

// 遍历所有Matcher
for (AbsMatcher matcher : matcherList) {
if (AptHub.routeTable.isEmpty()) {
// 没有路由表,都是隐式Intent
if (matcher.match(context, mRouteRequest.getUri(), null, mRouteRequest)) {
// 构造Intent
return finalizeIntent(context, matcher, null);
}
} else {
// 是否为隐式匹配的Matcher
boolean isImplicit = matcher instanceof AbsImplicitMatcher;
// 遍历路由表
for (Map.Entry<String, Class<?>> entry : entries) {
// mRouteRequest.getUri(): router://filter/module1
// 路由表entry: router://filter/module1 <--> Module1Activity.class
if (matcher.match(context, mRouteRequest.getUri(), isImplicit ? null : entry.getKey(), mRouteRequest)) {
// 构造Intent
return finalizeIntent(context, matcher, isImplicit ? null : entry.getValue());
}
}
}
}

callback(RouteResult.FAILED, String.format(
"Can not find an Activity that matches the given uri: %s", mRouteRequest.getUri()));
return null;
}

再继续看 Intent 的构造:

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
// class RealRouter

private Intent finalizeIntent(Context context, AbsMatcher matcher, @Nullable Class<?> target) {
// 拦截目标组件class
if (intercept(context, assembleClassInterceptors(target))) {
return null;
}
// 返回Intent or 实例
Object result = matcher.generate(context, mRouteRequest.getUri(), target);
if (result instanceof Intent) {
Intent intent = (Intent) result;
// 填充Intent,完善的参数配置
if (mRouteRequest.getExtras() != null && !mRouteRequest.getExtras().isEmpty()) {
intent.putExtras(mRouteRequest.getExtras());
}
if (mRouteRequest.getFlags() != 0) {
intent.addFlags(mRouteRequest.getFlags());
}
if (mRouteRequest.getData() != null) {
intent.setData(mRouteRequest.getData());
}
if (mRouteRequest.getType() != null) {
intent.setType(mRouteRequest.getType());
}
if (mRouteRequest.getAction() != null) {
intent.setAction(mRouteRequest.getAction());
}
return intent;
} else {
// 非Intent,返回null
...
return null;
}
}

可见,Intent 的构造是由不同的 Matcher 所决定的,隐式 Intent 和显式 Intent 的不同生成便是在 Matcher 中实现的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 显式Intent的Matcher
public abstract class AbsExplicitMatcher extends AbsMatcher {
...

@Override
public Object generate(Context context, Uri uri, @Nullable Class<?> target) {
...
// 没有type字段标注类型,所以直接用class判断继承关系
if (Activity.class.isAssignableFrom(target)) {
// activity返回intent
result = new Intent(context, target);
} else if (Fragment.class.isAssignableFrom(target)) {
// v4.fragment返回实例
result = target.newInstance();
} else if (android.app.Fragment.class.isAssignableFrom(target)) {
// fragment返回实例
result = target.newInstance();
}
return result;
}

}

隐式 Intent 同理,但更简单,只需返回一个携带 ACTION_VIEW 的 Intent 即可:

1
2
3
4
5
6
7
8
9
10
// 隐式Intent的Matcher
public abstract class AbsImplicitMatcher extends AbsMatcher {
...

@Override
public Object generate(Context context, Uri uri, @Nullable Class<?> target) {
return new Intent(Intent.ACTION_VIEW, uri);
}

}

那么,是如何匹配到合适的匹配器呢?答案就在 match() 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 隐式Intent
public boolean match(Context context, Uri uri, @Nullable String route, RouteRequest routeRequest) {
...
// Manifest是否有关联该隐式URI
ResolveInfo resolveInfo = context.getPackageManager().resolveActivity(
new Intent(Intent.ACTION_VIEW, uri), PackageManager.MATCH_DEFAULT_ONLY);
if (resolveInfo != null) {
if (uri.getQuery() != null) {
// 将uri的query参数添加到routeRequest的bundle
parseParams(uri, routeRequest);
}
return true;
}
return false;
}

// 显式Intent
public boolean match(Context context, Uri uri, @Nullable String route, RouteRequest routeRequest) {
return !isEmpty(route) && uri.toString().equals(route);
}

至此,跳转相关就分析完毕了,简单说就是根据请求,匹配到合适的 Matcher,Matcher 会生成合适的 Intent,然后将 RouteRequest 中的信息填充到 Intent 中,再 startActivity() 进行跳转即可。

而其中的 Matcher 的抽象使得开发者可以自定义自己的 Matcher,构造自定义的 Intent,使得路由流程更加地灵活,这是 ARouter 中不具备的一个有意思的亮点,接下来看另一个亮点,拦截器是如何实现单一拦截的。

合理的组件拦截

回到跳转方法,其中跳转前的操作就是拦截:

1
2
3
4
5
6
7
8
9
// class RealRouter

private Intent finalizeIntent(Context c, AbsMatcher matcher, @Nullable Class<?> target) {
// 拦截目标组件class
if (intercept(c, assembleClassInterceptors(target))) {
return null;
}
...
}

首先需要获取组件关联的拦截器,关联信息在初始化阶段就已经被加载,这里只需取出即可,不过还需加上动态配置的拦截器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// class RealRouter

private Set<String> assembleClassInterceptors(@Nullable Class<?> target) {
Set<String> finalInterceptors = new HashSet<>();
if (target != null) {
// 1. 取出组件对应的拦截器
String[] baseInterceptors = AptHub.targetInterceptors.get(target);
if (baseInterceptors != null && baseInterceptors.length > 0) {
Collections.addAll(finalInterceptors, baseInterceptors);
}
// 2. 移除需要动态跳过的拦截器
if (mRouteRequest.getRemovedInterceptors() != null) {
finalInterceptors.removeAll(mRouteRequest.getRemovedInterceptors());
}
}
// 3. 添加动态增加的拦截器
if (mRouteRequest.getAddedInterceptors() != null) {
finalInterceptors.addAll(mRouteRequest.getAddedInterceptors());
}
return finalInterceptors;
}

对于每一个跳转请求,都会进入到 intercept() 方法中:

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
// class RealRouter

private boolean intercept(Context context, Set<String> finalInterceptors) {
// 是否跳过拦截器
if (mRouteRequest.isSkipInterceptors()) {
return false;
}

if (finalInterceptors != null && !finalInterceptors.isEmpty()) {
for (String name : finalInterceptors) {
// 从缓存取实例
RouteInterceptor interceptor = mInterceptorInstance.get(name);
if (interceptor == null) {
// 没有实例则创建,并缓存
// 取出拦截器名字对应的class
Class<? extends RouteInterceptor> clz = AptHub.interceptorTable.get(name);
try {
Constructor<? extends RouteInterceptor> constructor = clz.getConstructor();
interceptor = constructor.newInstance();
mInterceptorInstance.put(name, interceptor);
} catch (Exception e) {
...
}
}
// 执行拦截,任意一个返回true则拦截
if (interceptor != null && interceptor.intercept(context, mRouteRequest)) {
...
return true;
}
}
}
return false;
}

拦截流程设计得简单,很容易理解,取出一个组件的声明的拦截器,再处理动态增减拦截器,再将这些拦截器实例化并缓存,然后每实例化一个就执行该拦截,任意一个拦截函数返回 true 即中断,否则继续执行其它拦截器。

最后说点什么

到这里,Router 的分析就结束了,那么 Router 相对于 ARouter 存在的意义是什么?在阅读源码后,我想,也许是『灵活』和『专注』吧,灵活在于赋予开发者更多的操作能力实现更酷的 idea,专注在于让 Provider 什么的一边去,只做好页面路由。