ARouter路由解析

6次阅读

共计 16743 个字符,预计需要花费 42 分钟才能阅读完成。

目录介绍

01. 原生跳转实现

02. 实现组件跳转方式

2.1 传统跳转方式
2.2 为何需要路由

03.ARouter 配置与优势

04. 跨进程组件通信

4.1 URLScheme
4.2 AIDL
4.3 BroadcastReceiver
4.4 路由通信注意要点

05.ARouter 的结构

06.ARouter 的工作流程

6.1 初始化流程
6.2 跳转页面流程

07.ARouter 简单调用 api

7.1 最简单调用
7.2 build 源码分析
7.3 navigation 分析

08.Postcard 信息携带
09.LogisticsCenter
10.DegradeService 降级容错服务
11.Interceptor 拦截器
12. 数据传输和自动注入
13. 多 dex 的支持
14.InstantRun 支持
15. 生成的编译代码

好消息

博客笔记大汇总【16 年 3 月到至今】,包括 Java 基础及深入知识点,Android 技术博客,Python 学习笔记等等,还包括平时开发中遇到的 bug 汇总,当然也在工作之余收集了大量的面试题,长期更新维护并且修正,持续完善……开源的文件是 markdown 格式的!同时也开源了生活博客,从 12 年起,积累共计 N 篇 [近 100 万字,陆续搬到网上],转载请注明出处,谢谢!
链接地址:https://github.com/yangchong2…
如果觉得好,可以 star 一下,谢谢!当然也欢迎提出建议或者问题,万事起于忽微,量变引起质变!

注解学习小案例

注解学习小案例,比较系统性学习注解并且应用实践。简单应用了运行期注解,通过注解实现了 setContentView 功能;简单应用了编译器注解,通过注解实现了防暴力点击的功能,同时支持设置时间间隔;使用注解替代枚举;使用注解一步步搭建简单路由案例。结合相应的博客,在来一些小案例,从此应该对注解有更加深入的理解……
开源项目地址:https://github.com/yangchong2…

01. 原生跳转实现
Google 提供的原声路由主要是通过 intent,可以分成显示和隐式两种。显示的方案会导致类之间的直接依赖问题,耦合严重;隐式 intent 需要的配置清单中统一声明,首先有个暴露的问题,另外在多模块开发中协作也比较困难。只要调用 startActivity 后面的环节我们就无法控制了,在出现错误时无能为力。
02. 实现组件跳转方式
2.1 传统跳转方式

第一种,通过 intent 跳转
第二种,通过 aidl 跳转
第三种,通过 scheme 协议跳转

2.2 为何需要路由

显示 Intent:项目庞大以后,类依赖耦合太大,不适合组件化拆分
隐式 Intent:协作困难,调用时候不知道调什么参数
每个注册了 Scheme 的 Activity 都可以直接打开,有安全风险
AndroidMainfest 集中式管理比较臃肿
无法动态修改路由,如果页面出错,无法动态降级
无法动态拦截跳转,譬如未登录的情况下,打开登录页面,登录成功后接着打开刚才想打开的页面
H5、Android、iOS 地址不一样,不利于统一跳转

03.ARouter 配置与优势
3.1 ARouter 的优势

如下所示

直接解析 URL 路由,解析参数并赋值
支持多模块项目
支持 InstantRun
允许自定义拦截器
ARouter 可以提供 IoC 容器
映射关系自动注册
灵活的降级策略

3.2 至于配置和使用
直接看 https://www.jianshu.com/p/fed…

04. 跨进程组件通信
4.1 URLScheme【例如:ActivityRouter、ARouter 等】

优势有:

基因中自带支持从 webview 中调用
不用互相注册(不用知道需要调用的 app 的进程名称等信息)

劣势有:

只能单向地给组件发送信息,适用于启动 Activity 和发送指令,不适用于获取数据 (例如:获取用户组件的当前用户登录信息)
需要有个额外的中转 Activity 来统一处理 URLScheme
如果设备上安装了多个使用相同 URLScheme 的 app,会弹出选择框(多个组件作为 app 同时安装到设备上时会出现这个问题)
无法进行权限设置,无法进行开关设置,存在安全性风险

4.2 AIDL

优势有:

可以传递 Parcelable 类型的对象
效率高
可以设置跨 app 调用的开关

劣势有:

调用组件之前需要提前知道该组件在那个进程,否则无法建立 ServiceConnection
组件在作为独立 app 和作为 lib 打包到主 app 时,进程名称不同,维护成本高

4.3 BroadcastReceiver

BroadcastReceiver + Service + LocalSocket。该方案是参考 cc 路由框架!

跨组件间通信实现的同时,应该满足以下条件:

每个 app 都能给其它 app 调用
app 可以设置是否对外提供跨进程组件调用的支持
组件调用的请求发出去之后,能自动探测当前设备上是否有支持此次调用的 app
支持超时、取消

4.4 路由通信注意要点
05.ARouter 的结构

ARouter 主要由三部分组成,包括对外提供的 api 调用模块、注解模块以及编译时通过注解生产相关的类模块。

arouter-annotation 注解的声明和信息存储类的模块
arouter-compiler 编译期解析注解信息并生成相应类以便进行注入的模块
arouter-api 核心调用 Api 功能的模块

annotation 模块

Route、Interceptor、Autowired 都是在开发是需要的注解。

compiler 模块
AutoWiredProcessor、InterceptorProcessor、RouteProcessor 分别为 annotation 模块对应的 Autowired、Interceptor、Route 在项目编译时产生相关的类文件。

api 模块
主要是 ARouter 具体实现和对外暴露使用的 api。

06.ARouter 的工作流程
6.1 初始化流程

初始化代码如下所示
/**

*/
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.”);
}
}
“`

之后接着看_ARouter.init(application) 这行代码,点击去查看
protected static synchronized boolean init(Application application) {
// 赋值上下文
mContext = application;
// 初始化 LogisticsCenter
LogisticsCenter.init(mContext, executor);
logger.info(Consts.TAG, “ARouter init success!”);
hasInit = true;
mHandler = new Handler(Looper.getMainLooper());
return true;
}

接下来看看 LogisticsCenter 里面做了什么
public class LogisticsCenter {
/**

*/
public synchronized static void init(Context context, ThreadPoolExecutor tpe) throws HandlerException {
mContext = context;
executor = tpe;
try {
long startInit = System.currentTimeMillis();
Set<String> routerMap;
//debug 或者版本更新的时候每次都重新加载 router 信息
// 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 generate by arouter-compiler.
// 加载 alibaba.android.arouter.routes 包下载的类
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 finish.
} 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.
// 导入 ARouter$$Root$$app.java, 初始化 Warehouse.groupsIndex 集合
((IRouteRoot) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.groupsIndex);
} else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_INTERCEPTORS)) {
// Load interceptorMeta
// 导入 ARouter$$Interceptors$$app.java,初始化 Warehouse.interceptorsIndex 集合
((IInterceptorGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.interceptorsIndex);
} else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_PROVIDERS)) {
// Load providerIndex
// 导入 ARouter$$Providers$$app.java,初始化 Warehouse.providersIndex 集合
((IProviderGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.providersIndex);
}
}

/******* 部分代码省略 ********/
} catch (Exception e) {
throw new HandlerException(TAG + “ARouter init logistics center exception! [” + e.getMessage() + “]”);
}
}
}
“`

综上所述,整个初始化的流程大概就是:

初始化运行时的上下文环境
初始化日志 logger
寻找 router 相关的类
解析并且缓存路由相关信息
初始化拦截服务

6.2 跳转页面流程

07.ARouter 调用 api
7.1 最简单调用

最简单的调用方式
ARouter.getInstance()
.build(“/user/UserFragment”)
.navigation();

7.2 build 源码分析

这个主要是添加跳转的路径
public Postcard build(String path) {
return _ARouter.getInstance().build(path);
}

然后把这个路径添加到默认的组中
/**

*/
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));
}
}
“`

7.3 navigation 分析

如下所示
final class _ARouter {
protected Object navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
try {
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.
DegradeService degradeService = ARouter.getInstance().navigation(DegradeService.class);
if (null != degradeService) {
degradeService.onLost(context, postcard);
}
}
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;
}

private Object _navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
// 没有上下文环境,就用 Application 的上下文环境
final Context currentContext = null == context ? mContext : context;
switch (postcard.getType()) {
case ACTIVITY:
// Build intent 构建跳转的 intent
final Intent intent = new Intent(currentContext, postcard.getDestination());
intent.putExtras(postcard.getExtras());
// Set flags. 设置 flag
int flags = postcard.getFlags();
if (-1 != flags) {
intent.setFlags(flags);
} else if (!(currentContext instanceof Activity)) {// Non activity, need less one flag.
// 如果上下文不是 Activity,则添加 FLAG_ACTIVITY_NEW_TASK 的 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 ((0 != postcard.getEnterAnim() || 0 != 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;
}
}

08.Postcard 信息携带

Postcard 主要为信息的携带者,内容是在构造一次路由信息的时候生产的,其继承于 RouteMeta。RouteMeta 是在代码编译时生成的内容,主要在初始化 WareHouse 时对跳转信息做了缓存。

看看代码如下所示
//Postcard 继承于 RouteMeta
public final class Postcard extends RouteMeta

// 然后看看编译生成的文件
/**

public class ARouter$$Group$$me implements IRouteGroup {
@Override
public void loadInto(Map<String, RouteMeta> atlas) {
atlas.put(“/me/ExperienceCouponActivity”, RouteMeta.build(RouteType.ACTIVITY, ExperienceCouponActivity.class, “/me/experiencecouponactivity”, “me”, null, -1, -2147483648));
atlas.put(“/me/ServiceActivity”, RouteMeta.build(RouteType.ACTIVITY, ServiceActivity.class, “/me/serviceactivity”, “me”, null, -1, -2147483648));
atlas.put(“/me/SettingActivity”, RouteMeta.build(RouteType.ACTIVITY, SettingActivity.class, “/me/settingactivity”, “me”, null, -1, -2147483648));
atlas.put(“/me/UdeskServiceActivity”, RouteMeta.build(RouteType.ACTIVITY, UdeskServiceActivity.class, “/me/udeskserviceactivity”, “me”, null, -1, -2147483648));
}
}
“`

10.DegradeService 降级容错服务

首先,自定义一个类,需要继承 DegradeService 类,如下所示
/**
* <pre>
* @author 杨充
* blog : https://github.com/yangchong211
* time : 2018/08/24
* desc : ARouter 路由降级处理
* revise:

*/
@Route(path = DegradeServiceImpl.PATH)
public class DegradeServiceImpl implements DegradeService {

static final String PATH = “/service/DegradeServiceImpl”;

@Override
public void onLost(Context context, Postcard postcard) {
if (context != null && postcard.getGroup().equals(“activity”)) {
Intent intent = new Intent(context, WebViewActivity.class);
intent.putExtra(Constant.URL, Constant.GITHUB);
intent.putExtra(Constant.TITLE, “github 地址 ”);
ActivityCompat.startActivity(context, intent, null);
}
}

@Override
public void init(Context context) {

}
}
“`

如何使用该降级方案,十分简单。
NavigationCallback callback = new NavCallback() {
@Override
public void onArrival(Postcard postcard) {
LogUtils.i(“ARouterUtils”+”— 跳转完了 ”);
}

@Override
public void onFound(Postcard postcard) {
super.onFound(postcard);
LogUtils.i(“ARouterUtils”+”— 找到了 ”);
}

@Override
public void onInterrupt(Postcard postcard) {
super.onInterrupt(postcard);
LogUtils.i(“ARouterUtils”+”— 被拦截了 ”);
}

@Override
public void onLost(Postcard postcard) {
super.onLost(postcard);
LogUtils.i(“ARouterUtils”+”— 找不到了 ”);
DegradeServiceImpl degradeService = new DegradeServiceImpl();
degradeService.onLost(Utils.getApp(),postcard);
}
};

11.Interceptor 拦截器

在 ARouter 模块的时候讲述 Interceptor 的使用,如果本次路由跳转不是走的绿色通道那么则会触发拦截器进行过滤。
final class _ARouter {
protected Object navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
/************ 部分代码省略 ************/

if (!postcard.isGreenChannel()) {// It must be run in async thread, maybe interceptor cost too mush time made ANR.
interceptorService.doInterceptions(postcard, new InterceptorCallback() {
/**

*
* @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;
}
}
“`

拦截器的初始化
在刚开始初始化的时候,就已经做了这个操作。
final class _ARouter {
static void afterInit() {
// Trigger interceptor init, use byName.
interceptorService = (InterceptorService) ARouter.getInstance().build(“/arouter/service/interceptor”).navigation();
}
}

InterceptorServiceImpl 的 init 方法:
@Route(path = “/arouter/service/interceptor”)
public class InterceptorServiceImpl implements InterceptorService {
@Override
public void init(final Context context) {
LogisticsCenter.executor.execute(new Runnable() {
@Override
public void run() {
if (MapUtils.isNotEmpty(Warehouse.interceptorsIndex)) {
// 循环遍历仓库中的拦截器
for (Map.Entry<Integer, Class<? extends IInterceptor>> entry : Warehouse.interceptorsIndex.entrySet()) {
Class<? extends IInterceptor> interceptorClass = entry.getValue();
try {
// 反射机制构造自定义的每一个拦截器实例
IInterceptor iInterceptor = interceptorClass.getConstructor().newInstance();
iInterceptor.init(context);
// 并将其添加在缓存中
Warehouse.interceptors.add(iInterceptor);
} catch (Exception ex) {
throw new HandlerException(TAG + “ARouter init interceptor error! name = [” + interceptorClass.getName() + “], reason = [” + ex.getMessage() + “]”);
}
}
interceptorHasInit = true;
logger.info(TAG, “ARouter interceptors init over.”);
synchronized (interceptorInitLock) {
interceptorInitLock.notifyAll();
}
}
}
});
}
}

拦截器的工作过程
@Route(path = “/arouter/service/interceptor”)
public class InterceptorServiceImpl implements InterceptorService {
@Override
public void doInterceptions(final Postcard postcard, final InterceptorCallback callback) {
if (null != Warehouse.interceptors && Warehouse.interceptors.size() > 0) {
// 检测是否初始化完所有的烂机器
checkInterceptorsInitStatus();
// 没有完成正常的初始化,抛异常
if (!interceptorHasInit) {
callback.onInterrupt(new HandlerException(“Interceptors initialization takes too much time.”));
return;
}
// 顺序遍历每一个拦截器,
LogisticsCenter.executor.execute(new Runnable() {
@Override
public void run() {
CancelableCountDownLatch interceptorCounter = new CancelableCountDownLatch(Warehouse.interceptors.size());
try {
_excute(0, interceptorCounter, postcard);
interceptorCounter.await(postcard.getTimeout(), TimeUnit.SECONDS);
// 拦截器的遍历终止之后,如果有还有没有遍历的拦截器,则表示路由事件被拦截
if (interceptorCounter.getCount() > 0) {// Cancel the navigation this time, if it hasn’t return anythings.
callback.onInterrupt(new HandlerException(“The interceptor processing timed out.”));
} else if (null != postcard.getTag()) {// Maybe some exception in the tag.
callback.onInterrupt(new HandlerException(postcard.getTag().toString()));
} else {
callback.onContinue(postcard);
}
} catch (Exception e) {
callback.onInterrupt(e);
}
}
});
} else {
callback.onContinue(postcard);
}
}

// 执行拦截器的过滤事件
private static void _excute(final int index, final CancelableCountDownLatch counter, final Postcard postcard) {
if (index < Warehouse.interceptors.size()) {
IInterceptor iInterceptor = Warehouse.interceptors.get(index);
iInterceptor.process(postcard, new InterceptorCallback() {
@Override
public void onContinue(Postcard postcard) {
// Last interceptor excute over with no exception.
counter.countDown();
// 如果当前没有拦截过滤,那么使用下一个拦截器
_excute(index + 1, counter, postcard); // When counter is down, it will be execute continue ,but index bigger than interceptors size, then U know.
}

@Override
public void onInterrupt(Throwable exception) {
// Last interceptor excute over with fatal exception.
postcard.setTag(null == exception ? new HandlerException(“No message.”) : exception.getMessage()); // save the exception message for backup.
counter.cancel();
}
});
}
}
}

12. 数据传输和自动注入
13. 多 dex 的支持

可查看 multidex 源码:
public class ClassUtils {
/**
* Identifies if the current VM has a native support for multidex, meaning there is no need for

*
* @return true if the VM handles multidex
*/
private static boolean isVMMultidexCapable() {
boolean isMultidexCapable = false;
String vmName = null;

try {
if (isYunOS()) {// YunOS 需要特殊判断
vmName = “‘YunOS'”;
isMultidexCapable = Integer.valueOf(System.getProperty(“ro.build.version.sdk”)) >= 21;
} else {// 非 YunOS 原生 Android
vmName = “‘Android'”;
String versionString = System.getProperty(“java.vm.version”);
if (versionString != null) {
Matcher matcher = Pattern.compile(“(\\d+)\\.(\\d+)(\\.\\d+)?”).matcher(versionString);
if (matcher.matches()) {
try {
int major = Integer.parseInt(matcher.group(1));
int minor = Integer.parseInt(matcher.group(2));
isMultidexCapable = (major > VM_WITH_MULTIDEX_VERSION_MAJOR)
|| ((major == VM_WITH_MULTIDEX_VERSION_MAJOR)
&& (minor >= VM_WITH_MULTIDEX_VERSION_MINOR));
} catch (NumberFormatException ignore) {
// let isMultidexCapable be false
}
}
}
}
} catch (Exception ignore) {

}

Log.i(Consts.TAG, “VM with name ” + vmName + (isMultidexCapable ? ” has multidex support” : ” does not have multidex support”));
return isMultidexCapable;
}
}
“`

14.InstantRun 支持

什么是 InstantRun 支持?
Android Studio 2.0 中引入的 Instant Run 是 Run 和 Debug 命令的行为,可以大幅缩短应用更新的时间。尽管首次构建可能需要花费较长的时间,Instant Run 在向应用推送后续更新时则无需构建新的 APK,因此,这样可以更快地看到更改。

15. 生成的编译代码

如下所示

关于其他内容介绍
01. 关于博客汇总链接

1. 技术博客汇总

2. 开源项目汇总

3. 生活博客汇总

4. 喜马拉雅音频汇总

5. 其他汇总

02. 关于我的博客

我的个人站点:www.yczbj.org,www.ycbjie.cn
github:https://github.com/yangchong211

知乎:https://www.zhihu.com/people/…

简书:http://www.jianshu.com/u/b7b2…

csdn:http://my.csdn.net/m0_37700275

喜马拉雅听书:http://www.ximalaya.com/zhubo…

开源中国:https://my.oschina.net/zbj161…

泡在网上的日子:http://www.jcodecraeer.com/me…

邮箱:yangchong211@163.com
阿里云博客:https://yq.aliyun.com/users/a… 239.headeruserinfo.3.dT4bcV
segmentfault 头条:https://segmentfault.com/u/xi…

掘金:https://juejin.im/user/593943…

正文完
 0