得意于众多项目和第三方库的开源,开发中使用几行代码即可实现复杂的功能,但使用只是源码库的搬运工,源码中真正牛逼的技术并不属于我们,所以对源码和开源库的学习成了Android开发者提升技能的必经之路,笔者也曾经认真学习了常用开源框架的原理和实现,足以在开发和面试中的问题,就此以为掌握了源码(有没有同道?),直到有一天自己去编写库,当面对框架设计、任务调度、任务并发、线程切换、缓存、文件等系列问题时,才发现自己的不足,也在反思自己的学习深度;其实框架中很多知识和代码都是经过时间的验证和优化过的,如:Glide的缓存、okhttp拦截实现、Retrofit的注解等,其细节完全可以帮助解决开发中的类似问题,源码的思想固然重要,但细节优秀的实现同样不容忽视,这里给出笔者总结的开源框架的学习方法:
- 了解开源框架的作用
- 掌握框架的使用方法
- 分析框架的工作原理
- 分析框架源码的架构和实现
- 深入框架细节分析功能模块的实现
- 总结收获
1、ARouter作用
谈起Arouter便不得不说组件化,随着项目的发展,开发的功能模块增多,项目和团队都会逐渐增大,即使分包和版本管理做的再好,还是无法避免在开发过程中所面临的问题:
- 开发过程中编译和修改的过程中会变得更加复杂,编译事件明显变长
- 整个项目为一个整体,代码牵一发而动全身,项目的维护难度将会增大;
- 伴随开发团队也会增加,严重耦合的代码和业务造成冲突不断,极大降低开发效率
为了解决这些问题组件化应运而生,相信组件化的使用和优势开发者深有体会,它很针对性的解决了以上问题:
- 组件可以独立开发、编译调试,缩短编译时间提高开发效率
- 组件间的解耦和代码的隔离,使功能模块和开发团队之间互不影响
但世界上没有绝对完美的事,它带来方便的同时也带来了一个问题:
- 组件解耦、代码隔离的同时也阻断了彼此的通信
那么实现组件间通信变成了首要要解决的问题,Android原生虽然可以实现脱离界面实现导航的目的,但真实使用时发现代码严重耦合,违背了我们组件化的目的,那么有没有一种简单易管理的通信方案呢?ARouter就是为此诞生,这里借用官方关于原生和路由的对比图:
关于ARouter的介绍这里提供一个官方的演讲内容:
2、ARouter的使用简介
知道了框架的作用,下面自然就是看看如何去使用,本文侧重于框架的源码和执行流程,不详细介绍框架的使用,因为下面分析源码的需要,这里只给出界面跳转使用,详细使用方法可以阅读Arouter的项目介绍:
- 实现界面跳转:根据跳转的功能需求,ARouter提供以下界面跳转方法
- 普通界面跳转
ARouter.getInstance().build("/login/activity").navigation()
- 携带Key—Value参数传递
//ARouter提供withString()方法,参数传入对应的Key、Value
ARouter.getInstance().build("/login/activity")
.withString("Name", "USer")
.withInt("Age", 10)
.navigation()
//使用Autowired注解自动转换,参数name为传递参数的Key(当变量名和Key相同时可不设置name)
@Autowired(name = "Name")
@JvmField var name : String? = null
@Autowired(name = "Age")
@JvmField var age = 0
3、ARouter工作原理
- ARouter的工作流程图
一行代码就搞定了组件间的通信,是不是充满了疑惑和期待?下面看看这一行代码究竟如何路由起来的?本文的分析基于编写的组件化Demo中的ARouter的使用,这里不对它的编写和使用做介绍,只分析它编译生成的文件和执行流程进行分析,本篇以Activity执行为例分析源码,先看一下项目的结构和编译生成的文件:
- 项目结构:包含base、app、login、share、componenbase组件
- 编译文件:编译生成routes和modularization包
3.1、ARouter执行流程
由上面使用知道,界面的路由跳转有一行代码完成,那流程的分析也就从这行神奇的代码开始:
ARouter.getInstance().build("/login/activity").navigation()
- build(String path)
ARouter.getInstance()单利获取ARouter实例,在ARouter.build()方法中使桥接模式将任务交给_ARouter的build()方法,方法中重点的就是最后一句话,直接调用build()创建PostCard实例:
//_ARouter.build()
protected Postcard build(String path, String group) {
if (TextUtils.isEmpty(path) || TextUtils.isEmpty(group)) {
throw new HandlerException(Consts.TAG + "Parameter is invalid!");
} else { ......}
return new Postcard(path, group);//创建PostCard保存路径和group
}
}
- PostCard.navigation():build()方法只创建了一个PostCard实例,貌似所有的任务都留给了navigation
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);//如果回调Callback不为null,回调onLast()
}
......
if (!postcard.isGreenChannel()) { //如果没有设置绿色通道,要回调事件拦截
......
} else {
return _navigation(context, postcard, requestCode, callback);
}
return null;
}
整个流程执行到这里,好像就做了三件事,但这三件事却完成了整个的导航过程:
- 在build()中创建PostCard实例储存路径
- 调用complete()方法解析postCard
- 调用_navigation()执行界面的跳转
- LogisticsCenter.completion(postcard)
在看详细的代码之前,先介绍下源码中出现的RouteMeta和Warehouse两个类,很容易看出这两个类都是保存数据的类:
- RouteMeta:保存每个跳转信息,如:路径、group、RouteType、优先级、参数等信息
- Warehouse:主要在初始化时缓存注解的Activity、IProvider、IInterceptor,主要用于缓存所有的路由信息
class Warehouse {
//保存group对应的IRouteGroup文件类
static Map<String, Class<? extends IRouteGroup>> groupsIndex = new HashMap<&g