组件化开发的目的是为了解耦提高业务的复用,各业务之间相互独立,如何跳转页面和数据传输就成为了首要解决的问题,阿里的Arouter的框架为组件化改造提供了一种思路,作为开发中的常用框架,有必要知道其实现原理。
今天就来分析一波常用模块arouter-api及arouter-compiler的源码实现。
一、arouter-compiler
1.思想转变
平常开发时,经常会有一些重复且无聊的模板代码需要手动敲入,如最早期大篇的findViewById、setOnClickListener等,这样的代码写的再多也没有什么提高,甚至给人一种android开发已经固化的错觉,直到一些框架如ButterKnife等出现,给我们带来了新的启示:可以把这些模板化的代码交给注解处理器自动生成一些辅助文件,在辅助文件中自动创建模板化代码。不仅仅是findViewById的操作,开发过程中只要是有规则重复性高的工作都可以借鉴这种方式实现。arouter-compiler就是为Arouter框架在编译期自动生成了模板文件,完成了后续工作,下面看一下arouter-compiler在编译期都做了哪些事情。
2.processor介绍
APT(Annotation Processing Tool)即注解处理器,是一种处理注解的工具,它对源代码文件进行检测,找出其中的Annotation,根据注解自动生成代码。使用APT的优点就是方便、简单,可以少写很多重复的代码。Arouter中有三大注解处理器:AutowiredProcessor、InterceptorProcessor、RouteProcessor,三者都继承自BaseProcessor,路由表的创建跟RouteProcessor有关,后面我们会着重分析RouteProcessor源码。
3.BaseProcessor
BaseProcessor作为Arouter的注解处理器基类,先看下其实现:
BaseProcessor.java
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
......省略部分代码
// Attempt to get user configuration [moduleName]
Map options = processingEnv.getOptions();
if (MapUtils.isNotEmpty(options)) {
moduleName = options.get(KEY_MODULE_NAME);//通过key=AROUTER_MODULE_NAME 获取moduleName
generateDoc = VALUE_ENABLE.equals(options.get(KEY_GENERATE_DOC_NAME));
}
if (StringUtils.isNotEmpty(moduleName)) {
moduleName = moduleName.replaceAll("[^0-9a-zA-Z_]+", "");//替换moduleName中不在0-9a-zA-Z之间的字符为空字符
logger.info("The user has configuration the module name, it was [" + moduleName + "]");
} else {
logger.error(NO_MODULE_NAME_TIPS);
throw new RuntimeException("ARouter::Compiler >>> No module name, for more information, look at gradle log.");
}
}
在init方法中,首先会通过getOptions()获取到map options,key为"AROUTER_MODULE_NAME"也就是我们在module级build.gradle中配置项
value为moduelName,替换moduleName中非数字、大小写的字符为空字符。
BaseProcessor主要事情就是获取moduleName,并按照一定规则修改moduleName。
4.RouteProcessor
RouteProcessor中有两个全局变量:
private Map> groupMap = new HashMap<>(); // ModuleName and routeMeta.
private Map rootMap = new TreeMap<>(); // Map of root metas, used for generate class file in order.
两个map存放的数据来源我们下面分析,这里只要关注到groupMap中value为Set集合,rootMap是一个TreeMap类型。看下注解处理器的核心方法process:
RouteProcessor.java
@Override
public boolean process(Set extends TypeElement> annotations, RoundEnvironment roundEnv) {
if (CollectionUtils.isNotEmpty(annotations)) {
Set extends Element> routeElements = roundEnv.getElementsAnnotatedWith(Route.class);//获取所有带Route注解的元素,返回Set集合
try {
logger.info(">>> Found routes, start... <<<");
this.parseRoutes(routeElements);//解析路由
} catch (Exception e) {
logger.error(e);
}
return true;
}
return false;
}
process中,会获取所有带Route注解的元素,并返回set集合,然后将set集合当参数传入parseRoutes(),看下parseRoutes()实现
RouteProcessor.java
private void parseRoutes(Set extends Element> routeElements) throws IOException {
if (CollectionUtils.isNotEmpty(routeElements)) {
// prepare the type an so on.
logger.info(">>> Found routes, size is " + routeElements.size() + " <<<");
rootMap.clear();
......省略部分代码
//重点一
for (Element element : routeElements) {
TypeMirror tm = element.asType();
Route route = element.getAnnotation(Route.class);
RouteMeta routeMeta;
// Activity or Fragment
if (types.isSubtype(tm, type_Activity) || types.isSubtype(tm, fragmentTm) || types.isSubtype(tm, fragmentTmV4)) {
// Get all fields annotation by @Autowired
Map paramsType = new HashMap<>();
Map injectConfig = new HashMap<>();
injectParamCollector(element, paramsType, injectConfig);
if (types.isSubtype(tm, type_Activity)) {
// Activity
logger.info(">>> Found activity route: " + tm.toString() + " <<<");
routeMeta = new RouteMeta(route, element, RouteType.ACTIVITY, paramsType);
} else {
// Fragment
logger.info(">>> Found fragment route: " + tm.toString() + " <<<");
routeMeta = new RouteMeta(route, element, RouteType.parse(FRAGMENT), paramsType);
}
routeMeta.setInjectConfig(injectConfig);
} else if (types.isSubtype(tm, iProvider)) { // IProvider
logger.info(">>> Found provider route: " + tm.toString() + " <<<");
routeMeta = new RouteMeta(route, element, RouteType.PROVIDER, null);
} else if (types.isSubtype(tm, type_Service)) { // Service
logger.info(">>> Found service route: " + tm.toString() + " <<<");
routeMeta = new RouteMeta(route, element, RouteType.parse(SERVICE), null);
} else {
throw new RuntimeException("The @Route is marked on unsupported class, look at [" + tm.toString() + "].");
}
categories(routeMeta);//重点二
}
.....省略部分代码
for (Map.Entry> entry : groupMap.entrySet()) {
String groupName = entry.getKey();
MethodSpec.Builder loadIntoMethodOfGroupBuilder = MethodSpec.methodBuilder(METHOD_LOAD_INTO)
.addAnnotation(Override.class)
.addModifiers(PUBLIC)
.addParameter(groupParamSpec);
List routeDocList = new ArrayList<>();
// Build group method body
Set groupData = entry.getValue();
for (RouteMeta routeMeta : groupData) {
.....省略部分代码
loadIntoMethodOfGroupBuilder.addStatement(
"atlas.put($S, $T.build($T." + routeMeta.getType() + ", $T.class, $S, $S, " + (StringUtils.isEmpty(mapBody) ? null : ("new java.util.HashMap(){{" + mapBodyBuilder.toString() + "}}")) + ", " + routeMeta.getPriority() + ", " + routeMeta.getExtra() + "))",
routeMeta.getPath(),
routeMetaCn,
routeTypeCn,
className,
routeMeta.getPath().toLowerCase(),
routeMeta.getGroup().toLowerCase());//重点三
routeDoc.setClassName(className.toString());
routeDocList.add(routeDoc);
}
// Generate groups
String groupFileName = NAME_OF_GROUP + groupName;
JavaFile.builder(PACKAGE_OF_GENERATE_FILE,
TypeSpec.classBuilder(groupFileName)
.addJavadoc(WARNING_TIPS)
.addSuperinterface(ClassName.get(type_IRouteGroup))
.addModifiers(PUBLIC)
.addMethod(loadIntoMethodOfGroupBuilder.build())
.build()
).build().writeTo(mFiler);////重点四
logger.info(">>> Generated group: " + groupName + "<<<");
rootMap.put(groupName, groupFileName);//重点五
docSource.put(groupName, routeDocList);
}
if (MapUtils.isNotEmpty(rootMap)) {
// Generate root meta by group name, it must be generated before root, then I can find out the class of group.
for (Map.Entry entry : rootMap.entrySet()) {
loadIntoMethodOfRootBuilder.addStatement("routes.put($S, $T.class)", entry.getKey(), ClassName.get(PACKAGE_OF_GENERATE_FILE, entry.getValue()));//重点六
}
}
......省略部分代码
// Write root meta into disk.
String rootFileName = NAME_OF_ROOT + SEPARATOR + moduleName;
JavaFile.builder(PACKAGE_OF_GENERATE_FILE,
TypeSpec.classBuilder(rootFileName)
.addJavadoc(WARNING_TIPS)
.addSuperinterface(ClassName.get(elementUtils.getTypeElement(ITROUTE_ROOT)))
.addModifiers(PUBLIC)
.addMethod(loadIntoMethodOfRootBuilder.build())
.build()
).build().writeTo(mFiler);//重点七
logger.info(">>> Generated root, name is " + rootFileName + " <<<");
}
}
parseRoutes()中需要关注的点比较多,下面一个一个分析
- 重点一:遍历通过Route注解获取到的所有元素,根据类型创建RouteMeta
- 重点二:将路由元数据RouteMeta添加至Set中
/**
* Sort metas in group.
*
* @param routeMete metas.
*/
private void categories(RouteMeta routeMete) {
if (routeVerify(routeMete)) {//验证routeMete,主要是验证其组名是否存在,不存在的话取path中首个/后字符串
logger.info(">>> Start categories, group = " + routeMete.getGroup() + ", path = " + routeMete.getPath() + " <<<");
Set routeMetas = groupMap.get(routeMete.getGroup());
if (CollectionUtils.isEmpty(routeMetas)) {//groupMap中不存在对应组的集合,创建TreeSet,按照path字母先后顺序排序
Set routeMetaSet = new TreeSet<>(new Comparator() {
@Override
public int compare(RouteMeta r1, RouteMeta r2) {
try {
return r1.getPath().compareTo(r2.getPath());
} catch (NullPointerException npe) {
logger.error(npe.getMessage());
return 0;
}
}
});
routeMetaSet.add(routeMete);
groupMap.put(routeMete.getGroup(), routeMetaSet);//groupMap放的是key=组名,value=同组下Set的元素
} else {
routeMetas.add(routeMete);
}
} else {
logger.warning(">>> Route meta verify error, group is " + routeMete.getGroup() + " <<<");
}
}
在categories()中,首先会验证group是否存在,不存在的话截取path首个‘/’后字符串作为组名(routeVerify()),然后根据组名从groupMap中获取某个组下路由元数据集合,
如果已经存在集合,则直接添加到set中,set特性无法加入相同元素(treeSet根据path进行排序,path相同的RouteMeta对象会视为同一对象),会导致同组同Path元素无法添加成功,app层面如果同module下创建路径相同的两个类,则类名中字母表靠前类可以通过路由获取或跳转。
如果不存在,则创建TreeSet,以路由元数据path字母表顺序排序添加数据,并将routeMete加入set,将set加入groupMap。
至此我们也知道了,groupMap下存储的是:同组下所有RouteMeta。-
重点三:为Arouter$$Group$$GroupName.java文件的loadInto()添加执行语句,将routeMeta按照指定字段加入atlas:
-
重点四:生成组文件,文件名为:"Arouter$$Group$$GroupName"
重点五:将组名和对应组文件存入rootMap,到此我们知道了,rootMap下存储的是:所有组生成的组文件名称。
-
重点六:为Arouter$$Root$$ModuleName.java文件的loadInto()添加执行语句,将Class extends IRouteGroup>> routes加入到routes:
重点七:生成root文件:Arouter$$Root$$ModuleName.java
5.总结:
arouter-compiler用来对Arouter中的注解进行处理,应用在引入Arouter框架后,在编译阶段,按照一定的样式生成组文件、root文件、provider文件等,文件内方法实现也由注解处理器指定。
对于java工程:其自动生成文件路径为module\build\generated\ap_generated_sources\debug\out\com\alibaba\android\arouter\routes。
对于kotlin或java与kotlin混编工程:其自动生成文件路径为module\build\generated\source\kapt\debug\com\alibaba\android\arouter\routes。
二、arouter-api
1.init()源码分析
arouter-api是应用层使用的api,我们从 ARouter.init()切入源码进行查看(1.5.0版本)。
Arouter.java
public static void init(Application application) {
if (!hasInit) {
logger = _ARouter.logger;
_ARouter.logger.info(Consts.TAG, "ARouter init start.");
hasInit = _ARouter.init(application);
if (hasInit) {
_ARouter.afterInit();
}
_ARouter.logger.info(Consts.TAG, "ARouter init over.");
}
}
init方法中先后调用了_ARouter的两个方法_ARouter.init(),_ARouter.afterInit(),我们先看下_ARouter.init():
_ARouter.java
protected static synchronized boolean init(Application application) {
mContext = application;
LogisticsCenter.init(mContext, executor);
logger.info(Consts.TAG, "ARouter init success!");
hasInit = true;
mHandler = new Handler(Looper.getMainLooper());
return true;
}
_ARouter.init()内部其实调用了LogisticsCenter.init(mContext, executor),注意其第二个参数,传入了一个线程池对象,executor的实现如下:
_ARouter.java
ThreadPoolExecutor executor = DefaultPoolExecutor.getInstance();
DefaultPoolExecutor.java
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(64),
new DefaultThreadFactory());
}
}
}
return instance;
}
线程池采用容量为64的数组阻塞队列,队列对元素FIFO(先进先出)进行排序。采用经典的“有界缓冲区”,由一个固定大小的数组保存由生产者产生插入并由消费者取出的元素。将元素放入已是最大容量的队列时阻塞队列,从空元素的队列中取出元素时同样阻塞队列。关于ArrayBlockingQueue的相关知识,有兴趣的同学可以自行查阅,下面看下LogisticsCenter.init(mContext, executor)。
LogisticsCenter.java
/**
* LogisticsCenter init, load all metas in memory. Demand initialization
*/
public synchronized static void init(Context context, ThreadPoolExecutor tpe) throws HandlerException {
mContext = context;
executor = tpe;
try {
......省略部分代码
if (registerByPlugin) {
logger.info(TAG, "Load router map by arouter-auto-register plugin.");
} else {
Set 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()));//重点四
}
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);
}
}
}
......省略部分代码
} catch (Exception e) {
throw new HandlerException(TAG + "ARouter init logistics center exception! [" + e.getMessage() + "]");
}
}
- 重点一:如果是debug或app版本发生变化(versionName或versionCode发生变化),重新构建路由表。
- 重点二:获取所有的dex文件,从dex文件中获取到所有文件名,遍历所有文件过滤出以"com.alibaba.android.arouter.routes"开始的文件(在上面定义的线程池进行遍历筛选),形成路由表。
- 重点三:将路由表存储在SharedPreferences,方便下次直接从SharedPreferences取路由表,随后把versionCode和versionName也存储在SharedPreferences,更新版本。
- 重点四:非debug且版本没有发生变化,从SharedPreferences取路由表数据。
- 重点五:遍历路由表,根据类型不同,分别加入到Warehouse的不同map集合中。
Warehouse代码如下,存储路由元数据及其它数据。
/**
* Storage of route meta and other data.
*
* @author zhilong Contact me.
* @version 1.0
* @since 2017/2/23 下午1:39
*/
class Warehouse {
// Cache route and metas
static Map> groupsIndex = new HashMap<>();
static Map routes = new HashMap<>();
// Cache provider
static Map providers = new HashMap<>();
static Map providersIndex = new HashMap<>();
// Cache interceptor
static Map> interceptorsIndex = new UniqueKeyTreeMap<>("More than one interceptors use same priority [%s]");
static List interceptors = new ArrayList<>();
static void clear() {
routes.clear();
groupsIndex.clear();
providers.clear();
providersIndex.clear();
interceptors.clear();
interceptorsIndex.clear();
}
}
下面看下_ARouter.afterInit()的实现:
_ARouter.java
static void afterInit() {
// Trigger interceptor init, use byName.
interceptorService = (InterceptorService) ARouter.getInstance().build("/arouter/service/interceptor").navigation();
}
该方法逻辑比较简单,通过ARouter为interceptorService赋值。
2.navigation()源码分析
navigation()经过多层方法重载,最后调用:
_ARouter.java
protected Object navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
......省略部分代码
LogisticsCenter.completion(postcard);//重点一
......省略部分代码
if (!postcard.isGreenChannel()) { // It must be run in async thread, maybe interceptor cost too mush time made ANR.
interceptorService.doInterceptions(postcard, new InterceptorCallback() {
/**
* Continue process
*
* @param postcard route meta
*/
@Override
public void onContinue(Postcard postcard) {
_navigation(context, postcard, requestCode, callback);
}
/**
* Interrupt process, pipeline will be destory when this method called.
*
* @param exception Reson of interrupt.
*/
@Override
public void onInterrupt(Throwable exception) {
if (null != callback) {
callback.onInterrupt(postcard);
}
logger.info(Consts.TAG, "Navigation failed, termination by interceptor : " + exception.getMessage());
}
});
} else {
return _navigation(context, postcard, requestCode, callback);//重点二
}
return null;
}
- 重点一:为postcard各字段赋值。
- 重点二:没有拦截器的情况,执行页面跳转或获取Fragment、Service、Provider等。
LogisticsCenter.java
/**
* Completion the postcard by route metas
*
* @param postcard Incomplete postcard, should complete by this method.
*/
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());
......省略部分代码
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;
}
}
}
- 重点一:从Warehouse.routes集合中根据path获取RouteMeta
- 重点二:将组文件Arouter$$Group$$GroupName.java中所有RouteMeta放入Warehouse.routes,放入内存。
- 重点三:将组文件Arouter$$Group$$GroupName.java从Warehouse.groupsIndex中移除。
- 重点四:Warehouse.routes添加数据后重新加载。
- 重点五:为postcard设置各个字段。
_ARouter.java
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;
}
_ARouter._navigation()才是最终进行页面跳转或者获取Fragment、Service、Provider等实例的地方。
- 重点一:当postcard的类型为activity时,navigation()最终通过startActivity()进行跳转。
- 重点二:当postcard的类型为provider时,navigation()返回provider实例。
- 重点三:当postcard的类型为fragment时,navigation()返回fragment实例。
三、总结
Arouter在编译期通过注解处理器RouteProcessor获取到所有添加Route注解的元素,将所有的元素按照组名不同存放在groupMap、rootMap中,遍历groupMap、rootMap,生成组文件及root文件。
app运行时,在init()中根据版本是否发生变化、是否是debug模式,决定重新构建路由表还是使用SharedPreferences中缓存的路由表。然后遍历路由表,根据类型不同,分别加入到Warehouse的不同map集合中。
在使用navigation()跳转到activity或者获取fragment、provider等对象的实例时,将Warehouse中索引map中数据添加到元数据map,再次调用completion()方法,为postcard设置路由信息,根据设置的路由信息及postcard type,决定是跳转进入其它activity,还是返回fragment、provider等类型实例。