0%

ARouter API 简单分析

前言

ARouter 使用已久,在组件化中非常有用,既解决了页面跳转的问题,同时还解决了同等级组件之间的依赖问题。这里结合源码分析一下其核心实现逻辑,本节主要分析其 API 的实现。同时结合之前一个简单的路由 中我们对路由的思考来看看 ARouter 是怎么做的。

基本功能

对于 ARouter 来说,其最基本的用法便是其初始化和初级 navigation 的使用。这里就从 init 和 navigation 的基本用法出发,看看其中的奥秘。

基于 ARouter GitHub 源码 arouter-api 1.5.0 版本分析

init

ARouter 使用了门面模式,真正的实现都在 _ARoutre 里。其初始化的方法调用链如下

核心逻辑在 LogisticsCenter.init(mContext, executor) 中

executor 是一个自定义的线程池,关于如何使用 ThreadPoolExecutor 构建线程池,了解过 AsyncTask 的同学一定不陌生,老生常谈了,这里就不再赘述。

ThreadPoolExecutor executor = DefaultPoolExecutor.getInstance();
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
public class DefaultPoolExecutor extends ThreadPoolExecutor {
// Thread args
private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
private static final int INIT_THREAD_COUNT = CPU_COUNT + 1;
private static final int MAX_THREAD_COUNT = INIT_THREAD_COUNT;
private static final long SURPLUS_THREAD_LIFE = 30L;

private static volatile DefaultPoolExecutor instance;

public static DefaultPoolExecutor getInstance() {
if (null == instance) {
synchronized (DefaultPoolExecutor.class) {
if (null == instance) {
instance = new DefaultPoolExecutor(
INIT_THREAD_COUNT,
MAX_THREAD_COUNT,
SURPLUS_THREAD_LIFE,
TimeUnit.SECONDS,
new ArrayBlockingQueue<Runnable>(64),
new DefaultThreadFactory());
}
}
}
return instance;
}
}

LogisticsCenter.init

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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
public synchronized static void init(Context context, ThreadPoolExecutor tpe) throws HandlerException {
mContext = context;
executor = tpe;

try {
long startInit = System.currentTimeMillis();
//billy.qi modified at 2017-12-06
//load by plugin first
loadRouterMap();
if (registerByPlugin) {
logger.info(TAG, "Load router map by arouter-auto-register plugin.");
} else {
Set<String> routerMap;

// It will rebuild router map every times when debuggable.
if (ARouter.debuggable() || PackageUtils.isNewVersion(context)) {
logger.info(TAG, "Run with debug mode or new install, rebuild router map.");
// These class was generated by arouter-compiler.
routerMap = ClassUtils.getFileNameByPackageName(mContext, ROUTE_ROOT_PAKCAGE);
if (!routerMap.isEmpty()) {
context.getSharedPreferences(AROUTER_SP_CACHE_KEY, Context.MODE_PRIVATE).edit().putStringSet(AROUTER_SP_KEY_MAP, routerMap).apply();
}

PackageUtils.updateVersion(context); // Save new version name when router map update finishes.
} else {
logger.info(TAG, "Load router map from cache.");
routerMap = new HashSet<>(context.getSharedPreferences(AROUTER_SP_CACHE_KEY, Context.MODE_PRIVATE).getStringSet(AROUTER_SP_KEY_MAP, new HashSet<String>()));
}

logger.info(TAG, "Find router map finished, map size = " + routerMap.size() + ", cost " + (System.currentTimeMillis() - startInit) + " ms.");
startInit = System.currentTimeMillis();

for (String className : routerMap) {
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)) {
// Load interceptorMeta
((IInterceptorGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.interceptorsIndex);
} else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_PROVIDERS)) {
// Load providerIndex
((IProviderGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.providersIndex);
}
}
}

logger.info(TAG, "Load root element finished, cost " + (System.currentTimeMillis() - startInit) + " ms.");

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() + "]");
}
}

在 init 方法中,优先会调用 loadRouterMap ,在这里会由 arouter-auto-register 插件在编译期插入的代码自动注册路由表,这样 registerByPlugin = true ,上述代码中整个 else 分支就不会执行了。现在假设我们没有使用 arouter-auto-register 插件,那么就会执行下面的加载逻辑了。整个加载逻辑很简单,在 debug 模式或者是版本更新的情况下,会每次重新加载一份新的 class 信息,这些信息保存在一个 map 中;并把这份信息用 sp 直接存起来(感觉有点粗暴);否则的话就每次从 sp 里读取类的信息。关键方法就是

1
routerMap = ClassUtils.getFileNameByPackageName(mContext, ROUTE_ROOT_PAKCAGE);

这个 ClassUtils 很有意思,是直接从 galaxy 的 SDK copy 的 🐶🐶。

1
2
3
4
5
6
package com.alibaba.android.arouter.utils;

// Copy from galaxy sdk ${com.alibaba.android.galaxy.utils.ClassUtils}

public class ClassUtils {
}

这里 getFileNameByPackageName() 这个方法顾名思义就是按按照包名获取文件名。 这个实现其实很有意思,他是直接从 dex 文件获取 class 名称的,这里细节很简单就不展开了,可以参考源码 ClassUtils 。DexFile 类已经 Deprecated 了,但是依旧可以了解一下,在运行时如何读获取 apk 在手机上的位置,以及如何去获取 dex.

这里的 ROUTE_ROOT_PAKCAGE=“com.alibaba.android.arouter.routes”,最终获取的结果如下:

其实就是获取了所有由注解生成器生成的 class ,因为这些 class 包含了所有的路由信息。

最后根据完整类名,对所有 class 简单做了一下归类。

1
2
3
4
5
6
7
for (String className : routerMap) {
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 condition...
}

归类信息,存储在了 WareHouse 中。可以再回顾一下上图中生成的 class 信息,每一个类的 classname 中, com.alibaba.android.arouter.routes.$$.xxx ,在 $$ 符后面有四类 packagename,包括 Group,Root,Interceptors 和 Providers。这些类之间的关系,简单来说, Group 是 Root,Interceptors , Providers 的子集。

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

class Warehouse {
// Cache route and metas
static Map<String, Class<? extends IRouteGroup>> groupsIndex = new HashMap<>();
static Map<String, RouteMeta> routes = new HashMap<>();

// Cache provider
static Map<Class, IProvider> providers = new HashMap<>();
static Map<String, RouteMeta> providersIndex = new HashMap<>();

// Cache interceptor
static Map<Integer, Class<? extends IInterceptor>> interceptorsIndex = new UniqueKeyTreeMap<>("More than one interceptors use same priority [%s]");
static List<IInterceptor> interceptors = new ArrayList<>();

static void clear() {
routes.clear();
groupsIndex.clear();
providers.clear();
providersIndex.clear();
interceptors.clear();
interceptorsIndex.clear();
}
}

可以看到所有的属性及方法都是静态的,你可以认为 WareHouse 就是一个运行期的全局仓库,存储了所有路由相关的信息。

这里以 groupsIndex 为例,需要注意的是,在初始化的过程中,只是创建了 IRouteRoot 这个接口的实现类的实例,并添加到了相应的集合中。但是 Group 这个类并没有被实例话,这样这个接口的 loadInto 方法不会被调用。也就是说 ARouter 初始化的时候,并不会初始化所有的路由表中生成的 class。如上图 WareHouse 中现在存的都是一些虚拟组,真正的 RouteMate 还没有被添加 WareHouse 中。

举个例子

IRouteRoot 的某一个实现类

1
2
3
4
5
6
7
public class ARouter$$Root$$app implements IRouteRoot {
@Override
public void loadInto(Map<String, Class<? extends IRouteGroup>> routes) {
routes.put("test", ARouter$$Group$$test.class);
routes.put("yourservicegroupname", ARouter$$Group$$yourservicegroupname.class);
}
}

ARouter$$Group$$test.class

1
2
3
4
5
6
7
8
9
10
11
public class ARouter$$Group$$test implements IRouteGroup {
@Override
public void loadInto(Map<String, RouteMeta> atlas) {
atlas.put("/test/activity1", RouteMeta.build(RouteType.ACTIVITY, Test1Activity.class, "/test/activity1", "test", new java.util.HashMap<String, Integer>(){{put("ser", 9); put("ch", 5); put("fl", 6); put("dou", 7); put("boy", 0); put("url", 8); put("pac", 10); put("obj", 11); put("name", 8); put("objList", 11); put("map", 11); put("age", 3); put("height", 3); }}, -1, -2147483648));
atlas.put("/test/activity2", RouteMeta.build(RouteType.ACTIVITY, Test2Activity.class, "/test/activity2", "test", new java.util.HashMap<String, Integer>(){{put("key1", 8); }}, -1, -2147483648));
atlas.put("/test/activity3", RouteMeta.build(RouteType.ACTIVITY, Test3Activity.class, "/test/activity3", "test", new java.util.HashMap<String, Integer>(){{put("name", 8); put("boy", 0); put("age", 3); }}, -1, -2147483648));
atlas.put("/test/activity4", RouteMeta.build(RouteType.ACTIVITY, Test4Activity.class, "/test/activity4", "test", null, -1, -2147483648));
atlas.put("/test/fragment", RouteMeta.build(RouteType.FRAGMENT, BlankFragment.class, "/test/fragment", "test", null, -1, -2147483648));
atlas.put("/test/webview", RouteMeta.build(RouteType.ACTIVITY, TestWebview.class, "/test/webview", "test", null, -1, -2147483648));
}
}

展开上述代码,结合上面所叙述的内容。经过 init 归类之后,IRouteRoot 的某一个实现类(实际情况可能有多个,是由注解决定的)ARouter$$Root$$app 被添加到了 WareHouse.groupsIndex 集合中。但是 ARouter$$Root$$app 内部承载着另一个集合,这个集合才是最重要的,包含所有的 path 和 RouteMate 信息,但是这些信息并没有被加载。

如何把所有元素都放到一个集合里 ?

关于这里的代码,还有一个有意思的点,就是如何向集合中添加元素。

一般情况下,我们往集合里添加元素一般都是这么写

1
2
3
list.add(element)

map.put("key","value")

但是我们看上面 class 分组后添加到 WareHouse 仓库中的写法就很有意思了,简单翻译一下就是

1
2
3
4
// 这里 new IRouteRoot 表达的意思是 IRouteRoot 这个接口的实现类
// 的一个实例,并不是这个接口本身的实例,接口的实例本身也没有意义。

new IRouteRoot().loadInto(hashmap)

看一下 IRouteRoot

1
2
3
4
public interface IRouteRoot {

void loadInto(Map<String, Class<? extends IRouteGroup>> routes);
}

他的一个实现类 ARouter$$Root$$app(是编译期 APT 生成的类,可以暂时忽略细节)

ARouter$$Root$$app
1
2
3
4
5
6
7
public class ARouter$$Root$$app implements IRouteRoot {
@Override
public void loadInto(Map<String, Class<? extends IRouteGroup>> routes) {
routes.put("test", ARouter$$Group$$test.class);
routes.put("yourservicegroupname", ARouter$$Group$$yourservicegroupname.class);
}
}

其实本质都是一样,就是把 IRouteGroup 添加到一个 map 中。这里可以理解为通过 IRouteRoot 把集合容器注入到具体实现中,这里这么做是因为这个 map 的 key 是路由分组的组名(关于这个分组的概念,可以参考 ARouter 的使用),这个组名是定义在注解上的,是在编译期获取的,这样就可以在编译期直接按照组名把所有元素添加到 map 中 ,尽管此时 map 只是一个方法参数,并没有真正的 map 被创建。然后在运行期注入这个 map 容器,就完成了对 WareHouse 这个仓库的真正创建。

当然,这里完全可以不这么做,编译期生成文件时,每个文件自己创建 map, 自己存数据,然后运行期把所有 map 中的数据在统一收集到 WareHouse 仓库中理论上也是可以的。

个人感觉,使用容器注入的方式实现起来更加的友好和高效。本质来说,目标就是把所有类名加载到同一个集合中。

思考

假设现在需要许多同等级业务组件把某个元素统一添加到一个集合中,但是这个集合属于上层业务,底层业务无法直接依赖整个集合的引用。也就是说不能直接 list.add(xxx),因为 list 在上层,不能被下层反向依赖。

1
2
3
4
5
6
7
8
public final class BusinessGod {
private List<String> allBusiness = new ArrayList<>();
private BusinessBlue mBusinessBlue;
private BusinessGreen mBusinessGreen;
private BusinessRed mBusinessRed;

........
}

如上 BusinessGod 作为顶级业务,依赖了 BusinessBlue,BusinessGreen,BusinessRed 这些底层业务。现在需要把一些核心信息收集到 allBusiness 这个集合中时,由于这个 引用无法被下层依赖,那么一种可行的做法便是依赖接口。我们定义接口

1
2
3
4
public interface IContainerInterface<T> {

void loadInto(List<? super T> list);
}

然后让所有业务实现这个接口即可

细节
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class BusinessBlue implements IContainerInterface<String>{

@Override
public void loadInto(List<? super String> list) {
list.add("this is blue");
}
}

public class BusinessRed implements IContainerInterface<String>{

@Override
public void loadInto(List<? super String> list) {
list.add("this is red");
}
}

public class BusinessGreen implements IContainerInterface<String> {

@Override
public void loadInto(List<? super String> list) {
list.add("this is green");
}
}

这样我们就可以在顶层业务中,通过注入集合到底层业务的方式收集核心信息了

1
2
3
4
5
6
public List<String> getAllBusiness() {
mBusinessBlue.loadInto(allBusiness);
mBusinessGreen.loadInto(allBusiness);
mBusinessRed.loadInto(allBusiness);
return allBusiness;
}

这是一个关于如何把元素添加到一个集合的简单思考。

说完了 init 下面看看 navigation。在 ARouter 里用 navigation 其实可以做许多事情,这里简答看一下,他是如何实现 Activity 跳转和 Fragment 获取的。

Activity 跳转的实现

简单起见,就以以下代码为例

1
2
3
ARouter.getInstance()
.build("/test/activity2")
.navigation();

getInstance 就是获取 ARouter 的单例。

build

通过 build 方法返回了一个 PostCard 实例。PostCard 的可以理解为是包含整个路由信息的一个包装类。

navigation 总的流程如下

这里核心逻辑有两处 LogisticsCenter.completion(postcard) 和 _navigation(context, postcard, requestCode, callback)

LogisticsCenter.completion(postcard)
completion
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
79
80
81
82
83
84
public synchronized static void completion(Postcard postcard) {
if (null == postcard) {
throw new NoRouteFoundException(TAG + "No postcard!");
}

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 {
if (ARouter.debuggable()) {
logger.debug(TAG, String.format(Locale.getDefault(), "The group [%s] starts loading, trigger by [%s]", postcard.getGroup(), postcard.getPath()));
}

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

if (ARouter.debuggable()) {
logger.debug(TAG, String.format(Locale.getDefault(), "The group [%s] has already been loaded, trigger by [%s]", postcard.getGroup(), postcard.getPath()));
}
} catch (Exception e) {
throw new HandlerException(TAG + "Fatal exception when loading group meta. [" + e.getMessage() + "]");
}

completion(postcard); // Reload
}
} else {
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()) {
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;
}
}
}

前面强调过,在 init 方法里,只是初始化了所有组的 class 信息,但组所在 class 中每一个元素是没有被初始化的。因此这里如果直接通过 path 去查找目标类是找不到的,因此可以理解为,每个组中第一次被使用的路由,肩负着整个组所有路由信息被加载到 WareHouse 仓库的重任,这个组的信息所有信息会被加载到 WareHouse.routes 这个集合中(准确来说是一个 map,path 为 key,RouteMate 为值的一个 HashMap)。同时会从 WareHouse.groupsIndex 中移除当前组,毕竟这一项已经没用了。然后再次递归调用一下自己,这一次就可以直接从 WareHouse.routes 这个集合中直接找到 path 对应的 RouteMate 了。如果再找不到,那就是什么地方出问题了,就报错了。

在初始化时只加载组信息的做法,可以说是非常巧妙,算的上是一种分治的思想。将所有路由 RouteMate 分组,减少初始化的压力,然后用到某个组的时候,才去初始化整个组内所有的 RouteMate 。这样,虽然可能会影响组内第一个使用的路由,但是总体来说还是值得的,毕竟减少了初始化的压力。甚至大部分情况下,用户使用时,常规的跳转可能也就那么几个,完全没有必要在初始化是加载所有的信息。当然,这种问题见仁见智,也许全部初始化也是一个不错的策略。

如果正确找到了目标 RouteMeta,在 else 分支里就会将目标类的所有信息传递到当前的 postcard 实例中。这里传递的信息包括目标类类名,路由优先级,路由类型,以及一些额外的参数信息,这些信息就包括我们在使用路由时执行 putInt,putString,putObject 传递的参数。ARouter 这里对对象的传递并没有直接传递对象,而是把对象反序列化为 Json ,当做普通的 String 进行传递和通信。

总的来说,成功调用 completion 方法之后,postcard 从编译期生成的类中获得了完整的信息,并且补充了额外的信息。可以说,此时 postcard 已经完整了,该有的信息都有了,就可以为所欲为了 😏😏😏。

在进行下一步之前,我们可以简单思考一下。这些目前为止,我们可以做什么 ?

1
2
3
ARouter.getInstance()
.build("/test/activity2")
.navigation();

其实到这里,我们通过一个简单的字符串 “/test/activity2” 就已经知道了很多信息

  • 他对应的类是 ‘com.alibaba.android.arouter.demo.testactivity.Test2Activity’
  • 对应类的类型是 Activity

当然,如果注解里配置的信息再多一点,我们还可以获得更多的信息,包括这个路由的优先级以及他的 group 等等。

可以看到,实现页面跳转只是冰山一角,因为整个目标类的信息都完全获取到了,难道做一下跳转页面就完事了吗?(当然对于一个 Activity 来说,这样也不错,但如果目标类不单单是个 Activity 呢?是不是可以做的别的事情呢?👻👻👻👻👻👻👻👻)

postcard 已经完全包含所有信息了,那么后续的逻辑展开就很简单了

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

// Set Actions
String action = postcard.getAction();
if (!TextUtils.isEmpty(action)) {
intent.setAction(action);
}

// Navigation in main looper.
runInMainThread(new Runnable() {
@Override
public void run() {
startActivity(requestCode, currentContext, intent, postcard, callback);
}
});

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

可以看到,这里通过 postcard.getType() 可以获取到我们的路由是打在了什么东西上,对于 Activity 类型来说,就是我们最最熟悉的 startActivity 操作了,对于其类型,比如 Fragment,可以直接获取他的实例。

可以看到,使用 ARouter 不仅仅可以实现页面跳转这么简单,本身也可以作为一个解决组件间依赖关系耦合的利器。

1
2
Fragment fragment = (Fragment) ARouter.getInstance().build("/test/fragment").navigation();
Toast.makeText(this, "找到Fragment:" + fragment.toString(), Toast.LENGTH_SHORT).show();

用 ARouter 实现页面跳转时还可以添加跳转动画,传参。这些其实都是对实现页面跳转的基础功能之上的装饰器写法,就不展开叙述了。

拦截器的实现,在上述逻辑中已有分析,其实就是使用接口对对代码流程进行逻辑分发的一种实现。这种思想和 okhttp3 中拦截器的思路是一致的,在这里就是通过中间层把 postcard 暴露出来,给你一次机会,让你做一些 hack 的事情,让业务的实现更自由而已。

总结

以上简单分析了一下 ARouter 中,路由 init 和 navigation 的实现。并就实现过程做了简单的思考。
当然,ARouter 的定位远不止如此,正如其 GitHub 描述所讲,作为一个依赖注入的框架使用也是可以的。这里通过一个类图,在总结一下本节所讲到的所有类,加深一下映象。

加个鸡腿呗.