一、什么是路由?
路由是指路由器从一个接口上收到数据包,根据数据路由包的目的地址进行定向并转发到另一个接口的过程。—百度百科
以Android为例,各个组件module可以看成一个个网络,而路由就可以看成是各个模块页面跳转的中转站,除了中转以外,路由还可以过滤或拦截一些不安全或不规范的跳转。
二、为什么用阿里ARouter
官方定义:A framework for assisting in the renovation of Android componentization (帮助 Android App 进行组件化改造的路由框架)
其实Android的跳转已经提供了Intent,Intent不仅可以显示跳转,也可以隐式跳转。那么ARouter为什么还会存在呢?让我们对比一下原生跳转和路由跳转:
1、原生显示跳转必须依赖于类,强耦合,而隐式跳转需要都在AndroidManifest里集中注册,协作麻烦;而路由跳转通过URL索引,低耦合,协作方便;
2、原生在通过startActivity启动跳转后就交由系统控制,并不能做拦截;路由跳转可以对跳转做过滤或拦截。
6eb2eafbd4c419c493dbbfb1b05064087cda2173.png通过对比,我们发现路由特别适合用来做跳转(尤其是组件间的通信及跳转),下面就来看一下阿里ARouter的集成及使用吧~
三、ARouter概述
ARouter 是一个用于帮助 Android App 进行组件化改造的框架 —— 支持模块间的路由、通信、解耦
适用于以下场景:
从外部URL映射到内部页面,以及参数传递与解析
跨模块页面跳转,模块间解耦
拦截跳转过程,处理登陆、埋点等逻辑
跨模块API调用,通过控制反转来做组件解耦
主要包括这些功能:
支持直接解析标准URL进行跳转,并自动注入参数到目标页面中
支持多模块工程使用
支持添加多个拦截器,自定义拦截顺序
支持依赖注入,可单独作为依赖注入框架使用
支持InstantRun
支持MultiDex(Google方案)
映射关系按组分类、多级管理,按需初始化
支持用户指定全局降级与局部降级策略
页面、拦截器、服务等组件均自动注册到框架
支持多种方式配置转场动画
支持获取Fragment
完全支持Kotlin以及混编(配置见文末 其他#5)
支持第三方 App 加固(使用 arouter-register 实现自动注册)
支持生成路由文档
提供 IDE 插件便捷的关联路径和目标类
ARouter使用
这里只介绍ARouter最基础的用法–Activity跳转,其他用法如通过URL跳转、解析参数、声明拦截器、处理跳转结果、声明服务等,请详细阅读ARouter官网使用说明
1、添加依赖和配置
android {
defaultConfig {
javaCompileOptions {
annotationProcessorOptions {
arguments = [AROUTER_MODULE_NAME: project.getName()]
}
}
}
}
dependencies {
// 替换成最新版本, 需要注意的是api
// 要与compiler匹配使用,均使用最新版可以保证兼容
compile 'com.alibaba:arouter-api:x.x.x'
annotationProcessor 'com.alibaba:arouter-compiler:x.x.x'
}
// 旧版本gradle插件(< 2.2),可以使用apt插件,配置方法见文末'其他#4'
// Kotlin配置参考文末'其他#5'
2、添加注解
// 在支持路由的页面上添加注解(必选)
// 这里的路径需要注意的是至少需要有两级,/xx/xx
@Route(path = "/test/activity")
public class YourActivity extend Activity {
...
}
3、初始化SDK
if (isDebug()) { // 这两行必须写在init之前,否则这些配置在init过程中将无效
ARouter.openLog(); // 打印日志
ARouter.openDebug(); // 开启调试模式(如果在InstantRun模式下运行,必须开启调试模式!线上版本需要关闭,否则有安全风险)
}
ARouter.init(mApplication); // 尽可能早,推荐在Application中初始化
4、发起路由操作
// 1. 应用内简单的跳转(通过URL跳转在'进阶用法'中)
ARouter.getInstance().build("/test/activity").navigation();
// 2. 跳转并携带参数
ARouter.getInstance().build("/test/1")
.withLong("key1", 666L)
.withString("key3", "888")
.withObject("key4", new Test("Jack", "Rose"))
.navigation();
四、Arouter核心思路
ARouter通过Apt技术(APT,就是Annotation Processing Tool 的简称,就是可以在代码编译期间对注解进行处理,并且生成Java文件,减少手动的代码输入。),生成保存路径(路由path)和被注解(@Router)的组件类的映射关系的类,利用这些保存了映射关系的类,Arouter根据用户的请求postcard(明信片)寻找到要跳转的目标地址(class),使用Intent跳转。
原理很简单,可以看出来,该框架的核心是利用apt生成的映射关系,这里要用到Apt技术,读者可以自行搜索了解一下。
分析
在自定义Application中进行初始化:
// 尽可能早,推荐在Application中初始化
ARouter.init(Application.this);
最终调用了LogisticsCenter.init(mContext, executor)
//LogisticsCenter.java
public synchronized static void init(Context context, ThreadPoolExecutor tpe) throws HandlerException {
Set<String> routerMap;
//1、遍历“com.alibaba.android.arouter.routes”路径下的类并把其加入到set中
if (ARouter.debuggable() || PackageUtils.isNewVersion(context)) {
// 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();
}
// Save new version name when router map update finishes.
PackageUtils.updateVersion(context);
} 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>()));
}
//2、遍历set,将root、group、provider分类并填充到Warehouse路由表中
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);
}
}
}
}
LogisticsCenter.init方法比较长,上面只保留了核心代码,我们来看上面这种加载方式,PackageUtils.isNewVersion(context)中判断SharedPreferences(后面简称sp)里面是否有存储versionName及versionCode,如果没有或者他们有更新的时候,需要重新加载一次com.alibaba.android.arouter.routes这个路径下的类名并填充到Set中,否则直接从sp中取数据并赋值到Set中去。接着就开始遍历这个Set,并通过Class.forName(className)这种反射方式去实例化类并调用类中的loadInto方法将注解对应的索引信息添加到Warehouse路由表中。画个图来总结一下:
587163-75a58236057aacee.webp.jpg
ARouter跳转
ARouter跳转时,直接使用ARouter.getInstance().build("xxx/xxx").navigation()即可完成跳转,那我们就来看一下源码,看看里面都做了什么,首先是build方法:
/**
* 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 = navigation(PathReplaceService.class);
if (null != pService) {
path = pService.forString(path);
}
return build(path, extractGroup(path));
}
}
/**
* Build postcard by uri
*/
protected Postcard build(Uri uri) {
if (null == uri || TextUtils.isEmpty(uri.toString())) {
throw new HandlerException(Consts.TAG + "Parameter invalid!");
} else {
PathReplaceService pService = ARouter.getInstance().navigation(PathReplaceService.class);
if (null != pService) {
uri = pService.forUri(uri);
}
return new Postcard(uri.getPath(), extractGroup(uri.getPath()), uri, null);
}
}
/**
* Build postcard by path and group
*/
protected Postcard build(String path, String group) {
if (TextUtils.isEmpty(path) || TextUtils.isEmpty(group)) {
throw new HandlerException(Consts.TAG + "Parameter is invalid!");
} else {
PathReplaceService pService = ARouter.getInstance().navigation(PathReplaceService.class);
if (null != pService) {
path = pService.forString(path);
}
return new Postcard(path, group);
}
}
三个方法中都有,PathReplaceService pService = ARouter.getInstance().navigation(PathReplaceService.class),这个类是用来预处理path和uri的,调用方需要实现PathReplaceService就可以做预处理,如果不实现,默认pService==null,那么直接走下面的去初始化Postcard实体类。
接着来看navigation方法,因为build方法返回的是PostCard类,所以调用的是PostCard类的navigation方法,经过一系列跳转,最终来到_ARouter.getInstance().navigation(mContext, postcard, requestCode, callback) :
protected Object navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
try {
LogisticsCenter.completion(postcard);
} catch (NoRouteFoundException ex) {
logger.warning(Consts.TAG, ex.getMessage());
}
return null;
}
if (null != callback) {
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;
}
去除了部分无关代码,只保留了核心代码,首先调用了LogisticsCenter.completion方法,我们追进去看看:
//LogisticsCenter.java
/**
* Completion the postcard by route metas
*
* @param postcard Incomplete postcard, should complete by this method.
*/
public synchronized static void completion(Postcard postcard) {
// 从仓库的路由地址清单列表中拿到对应的RouteMeta
RouteMeta routeMeta = Warehouse.routes.get(postcard.getPath());
if (null == routeMeta) {
// 根据一级地址,拿到对应的路由地址清单的文件类(ARouter$$Root$$工程名)
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 {
IRouteGroup iGroupInstance = groupMeta.getConstructor().newInstance();
iGroupInstance.loadInto(Warehouse.routes);
Warehouse.groupsIndex.remove(postcard.getGroup());
} 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;
}
}
}
这个类很长,但是逻辑还是很清晰的:首先从Warehouse路由表的routes中获取RouteMeta,但是第一次获取的时候为空(因为init时只填充了Warehouse路由表的groupsIndex、interceptorsIndex、providersIndex,还记得吗?),接着从Warehouse.groupsIndex中根据group的名字找到对应的group索引,并将生成的索引类的map数据加载到Warehouse.routes中,然后把Warehouse.groupsIndex中对应的group删除掉,以免重复加载数据,然后调用了completion(postcard)进行重新加载。此时Warehouse.routes已经不为空,根据path获取对应的RouteMeta,就会走到else逻辑中,先是对PostCard设置了一堆属性,最后对IProvider的子类进行了初始化并加载到Warehouse.providers中,同时也设置到PostCard中,并给PROVIDER和FRAGMENT设置了绿色通道(不会被拦截)。总结一下:主要逻辑就是通过Warehouse.groupsIndex找到对应的group并进行加载,实现了分组加载路由表。
我们继续回到navigation方法中往下走,首先通过postcard.isGreenChannel()判断是否会拦截,如果拦截,就会走interceptorService的逻辑(interceptorService是在afeterInit中初始化的),否则就走到了_navigation逻辑中,那么来看_navigation方法:
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;
}
private void startActivity(int requestCode, Context currentContext, Intent intent, Postcard postcard, NavigationCallback callback) {
if (requestCode >= 0) {
// Need start for result
if (currentContext instanceof Activity) {
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);
}
}
最终ARouter跳转Activity最终也是用原生的Intent实现的,如果navigation()不传入context,则使用初始化时Application作为context,如果是FRAGMENT、PROVIDER、CONTENT_PROVIDER、BOARDCAST,通过反射方式初始化并返回即可。
源码分析之拦截处理
参考文档
链接:路由框架ARouter使用及源码解析
链接:ARouter源码解析
链接:ARouter 官方文档
网友评论