转载请注明出处:王亟亟的大牛之路
有一段时间没更新博客了,最近也没学什么新东西,正好组里小伙在做路由跳转的一个“公共库“,然后正好最近这样的轮子不少,我也就跟着看看,学习一下人家的思路,然后推荐给大家这个库
https://github.com/alibaba/ARouter
分析讲解之前先安利:https://github.com/ddwhan0123/Useful-Open-Source-Android (总有些你用得到的东西,欢迎补issues)
什么是路由跳转?为什么要用路由跳转?
路由跳转:
web开发框架一般支持用户设置路由表,让表内的页面/层级,产生可互相跳转,转发等行为(如果理解不正确请指出)
要用的理由1:
项目大了就无法获取到其他包的Activity.class了
要用的理由2:
逻辑清晰,比较语义化,清楚的知道跳转路径和目的地
要用的理由3:
不单单可以应用于普通Activity还可以与浏览器做一些业务逻辑。(如果有遗漏请指出)
ARouter所实现的功能:
支持直接解析URL进行跳转、参数按类型解析,支持Java基本类型(*)
支持应用内的标准页面跳转,API接近Android原生接口
支持多模块工程中使用,允许分别打包,包结构符合Android包规范即可(*)
支持跳转过程中插入自定义拦截逻辑,自定义拦截顺序(*)
支持服务托管,通过ByName,ByType两种方式获取服务实例,方便面向接口开发与跨模块调用解耦(*)
映射关系按组分类、多级管理,按需初始化,减少内存占用提高查询效率(*)
支持用户指定全局降级策略
支持获取单次跳转结果
丰富的API和可定制性
被ARouter管理的页面、拦截器、服务均无需主动注册到ARouter,被动发现 支持Android N推出的Jack编译链
内容来自:https://github.com/alibaba/ARouter/blob/master/README_CN.md
案例分析
官方提供了一个demo.apk,下载下来跑了跑,长这样
我们跟着demo去看他内部的实现,顺道看下how to use?
初始化:
在你要使用这个库之前,必须初始化,也就是调用
protected static synchronized boolean init(Application application)
其实这方法长这样,他也没叫你穿Context了,很直白直接叫你传Application,那我们就要在自定义Application里的onCreate里把他给初始化了,不初始化的话会丢一个
throw new InitException("ARouterCore::Init::Invoke init(context) first!");
日志:
内部维护了一个简单的log类:ILogger,也就是一些简单的打日志的那些东西 .d .e .i
这些,主要是为了给予这个类一个状态“是否打印日志“
而是否打印日志,调用
static synchronized void openLog()
在内部调用了
logger.showLog(true);
然后就有log了,无论是跳转过程还是拼接过程都可以看到具体内容,便与调试
调试:
因为log已经可以很好的帮你监控跳转行为的各个流程,官方缩写的调试方法更简便,当然首先你得开启
static synchronized void openDebug()
开完之后有什么区别呢?
当你调用navigation()方法后,他会弹一个Toast,告诉用户一些路由地址和分组的值。
路由地址很好理解,那组的概念又是什么呢?
跳转:
Activity之间的跳转是最为常见的了,ARouter对其进行非常有效的封装,像这样
ARouter.getInstance().build("/test/1")
.withLong("key1", 666L)
.withString("key3", "888")
.navigation();
我们来看一下他是如何实现的
首先先获取ARouter的实例,内部没有什么复杂操作,首先判断有没有初始化,如果初始化了再盼空,如果为空就创建一个ARouter对象,然后将其返回。
获取实例之后,先构建路径build
protected Postcard build(String path) {
if (StringUtils.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));
}
}
首先先判空,如果路径没东西就抛异常
不为空之后把时间逻辑交由PathReplaceService接口来处理
PathReplaceService 接口用于处理path相关逻辑,如果要自定义path处理方法可自行二次实现
分发完后调用了另外个build方法
protected Postcard build(String path, String group) {
if (StringUtils.isEmpty(path) || StringUtils.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);
}
}
行为几乎一致,但是这里把我们的路径分配到了默认组内并生成新的Postcard对象返回
看到这里有点尴尬,Postcard是什么鬼?
Postcard在com.alibaba.android.arouter.facade目录下是一个包含路线图的容器
里面有一些我们一看就懂的字段
private Uri uri;
private Object tag;
private Bundle mBundle;
private int flags = -1;
private int timeout = 300;
private IProvider provider;
private boolean greenChannal;
很明显他就是整个路由行为的一个载体,可分配url,group,path等等,既然是一个是载体,那我们就不管他干啥,反正就是一个带信息传递用的“快递小哥”
无论怎么创建其实都是走的下面这个构造函数,字面就很好理解,设置的path和group,Bundle有就有没有就创建,uri暂时是空
public Postcard(String path, String group, Uri uri, Bundle bundle) {
setPath(path);
setGroup(group);
setUri(uri);
this.mBundle = (null == bundle ? new Bundle() : bundle);
}
例子里我们是传参了,也就是对bundle进行了一系列的native的put操作。
navigation方法经过一系列的方法又回到了
ARouter.getInstance().navigation(context, this, -1, callback);
protected Object navigation(final Context context, final Postcard postcard, final int requestCode, NavigationCallback callback)
在调用之初
requestCode 是 -1
postcard 就是前面创建的Postcard对象
callback是个null
Context 是Application的context对象
protected Object navigation(final Context context, final Postcard postcard, final int requestCode, NavigationCallback callback) {
try {
//注册监听,去拆对象里的属性,做包装
LogisticsCenter.completion(postcard);
} catch (NoRouteFoundException ex) {
logger.warning(Consts.TAG, ex.getMessage());
//刚才提到的debuggable为true时候打出一些内容
if (debuggable()) { // Show friendly tips for user.
Toast.makeText(mContext, "There's no route matched!\n" +
" Path = [" + postcard.getPath() + "]\n" +
" Group = [" + postcard.getGroup() + "]", Toast.LENGTH_LONG).show(
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);
//因为ARouter实例不为空所以接口不为空所以把Lost⌚️分发了出去
if (null != degradeService) {
degradeService.onLost(context, postcard);
}
}
return null;
}
//如果回调不为空就把监听到的回调进行分发
if (null != callback) {
callback.onFound(postcard);
}
//绿色异步通道的处理
if (!postcard.isGreenChannal()) { // It must be run in async thread, maybe interceptor cost too mush time made ANR.
LogisticsCenter.interceptions(postcard, new InterceptorCallback() {
/**
* Continue process
*
* @param postcard route meta
*/
@Override
public void onContinue(Postcard postcard) {
_navigation(context, postcard, requestCode);
}
/**
* Interrupt process, pipeline will be destory when this method called.
*
* @param exception Reson of interrupt.
*/
@Override
public void onInterrupt(Throwable exception) {
logger.info(Consts.TAG, "Navigation failed, termination by interceptor : " + exception.getMessage());
}
});
} else {
return _navigation(context, postcard, requestCode);
}
return null;
}
上面有一些新名词,诸如绿色通道等,这部分可以去翻官方的readme,里面有介绍
这里看到,最后又调用了
private Object _navigation(final Context context, final Postcard postcard, final int requestCode)
这是具体跳转行为的一个方法,他支持 多种类型的postcard type,我们主要看下ACTIVITY相关的
case ACTIVITY:
// 构建 intent
Intent intent = new Intent(currentContext, postcard.getDestination());
intent.putExtras(postcard.getExtras());
// 设置 flags.
int flags = postcard.getFlags();
if (-1 != flags) {
intent.setFlags(flags);
} else {
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
}
// Judgment activity start type.
if (requestCode > 0) { // RequestCode exist, tell us user's want startActivityForResult, so this context must son of activity.
((Activity) currentContext).startActivityForResult(intent, requestCode);
} else {
currentContext.startActivity(intent);
}
break;
最终实现就是我们常用的 startActivity和startActivityForResult操作
总结:
其实它做的就是普通的跳转的行为,但是对跳转进行了很丰富的二次实现。
1.降低了协同开发的成本,因为你根本不在意跳转目的地是否真的存在,按照预先定下来的url跳转就好(包括传参数)。
2.无论外部内部跳转变得更简单,反正初始化就会构建一个路由管理器,无需自己管理跳转过程。
3.测试方便,跟web测转发一样,单元测试实现性更强。
本来还想介绍下ARouter的浏览器拦截和自定义url拼接,但是篇幅问题,这篇就不写了,以后有机会再写吧
类似实现的开源库:
https://github.com/mzule/ActivityRouter
https://github.com/joyrun/ActivityRouter