ARouter
帮助 Android App 进行组件化改造的路由框架。
一个最显著的作用就是实现两个互相不依赖的 module 进行页面跳转和数据传递,是项目组件化中衔接各 module 页面跳转通信很好的实现方式。
如何使用
官方 github 上的说明文档还是蛮清晰明了易上手的 在这我更想记录我对它的源码分析和理解。
源码分析
先从初始化说起,
//首先会在 Application 里调用 ARouter 的 init() 方法
public static void init(Application application) {
//hasInit 是用 volatile 修饰的,
//volatile 是 Java 虚拟机提供的最轻量级的同步机制,volatile 修饰的变量对所有线程具有可见性
//也就是说可以认为任一线程获取到的值就是实际值,并非缓存值
if (!hasInit) {
//调用 _ARouter 的 init 方法进行实际初始化
hasInit = _ARouter.init(application);
if (hasInit) {
_ARouter.afterInit();
}
}
}
//_ARouter
protected static synchronized boolean init(Application application) {
//入参赋值给了 Context 类型对象,看来是要用 Application 的 Context 类型功能
mContext = application;
//executor 是个线程池处理器,默认线程数是设备 cpu数 + 1
LogisticsCenter.init(mContext, executor);
//这里也有一个标记位
hasInit = true;
//创建一个主线程的 Handler 对象,估计用来做主线程相关的操作,例如 UI 更新等
mHandler = new Handler(Looper.getMainLooper());
//返回结果给前面的 hasInit,这样一来就会执行 _ARouter 的 afterInit() 方法
return true;
}
//再来看看 LogisticsCenter 的 init() 方法
public synchronized static void init(Context context, ThreadPoolExecutor tpe) {
mContext = context;
executor = tpe;
//这里有个通过插件方式的加载,不过看似好像废弃了,我们直接看 else 部分
Set routerMap;
if (ARouter.debuggable() || PackageUtils.isNewVersion(context)) {
//ROUTE_ROOT_PAKCAGE 指的是 ARoute 包名,~这个package单词是拼错了吗,还是故意这么写~
//这个工具方法作用就是找出该包下面的所有类名
//这里有个疑问,如果是 ARoute 自己的类,那每次应该都一样,感觉没用。结合这个变量名 routerMap,
//我想应该存的是要路由的类名,有可能是通过 @Route,这里先记着。
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);
} else {
//如果 debug 没开或者不是新包或者新版本的话就从缓存里取
//所以应该说,只要是第一次跑,都会执行上面 if 语句
routerMap = new HashSet<>(context.getSharedPreferences(AROUTER_SP_CACHE_KEY, Context.MODE_PRIVATE).getStringSet(AROUTER_SP_KEY_MAP, new HashSet()));
}
//遍历 roterMap,缓存一些对象,看循环内的逻辑,可见 routerMap 应该是 ARoute 包自身的类名集合
for (String className : routerMap) {
//根据类名判断,结合源码其实可以知道,要找的类就是 routes 包下面的
//ARouter$$Root$$arouterapi
//ARouter$$Providers$$arouterapi
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);
}
}
}
路径注解,就是在具体 Activity 页面上添加的 @Route 注解标识路径,
@Route(path = "/xxx/xxx/xxx")
//像这样在指定 Activity 上加上路径说明,
//不过 Route 这个注解其实还可以配置其他值,例如 name,group
接下来就是进行页面跳转时的路由操作,
//像这样可实现路由跳转
ARouter.getInstance().build("/xxx/xxx/xxx").navigation();
//先看 ARouter 的 build() 方法
public Postcard build(String path) {
//调用了 _ARouter 的 build() 方法,返回 Postcard 类型对象
return _ARouter.getInstance().build(path);
}
Postcard
这个概念说是代表着包含着路由表信息的实体,继承自 RouteMeta,这个父类倒是包含了路由的一些基础信息。总之路由过程中,它应该就是那个载体了。另外还可配置切换动画,用于页面转场效果。
//继续 _ARouter 的 build() 方法
protected Postcard build(String path) {
//这个 Service 会从刚才初始化时遍历 routeMap 过程的 Warehouse.providersIndex 里去找
//但根据分析肯定找不到,然后会去重建,但我经过最新版本 1.5.1 的断点也没看到创建成功,
//我们就按代码实际运行的逻辑来
PathReplaceService pService = ARouter.getInstance().navigation(PathReplaceService.class);
if (null != pService) {
path = pService.forString(path);
}
//这里会先提取 group 字符串,假设路径为 /aaa/bbb/ccc,那么 aaa 就是这里的 group
return build(path, extractGroup(path), true);
}
protected Postcard build(String path, String group, Boolean afterReplace) {
if (!afterReplace) {
PathReplaceService pService = ARouter.getInstance().navigation(PathReplaceService.class);
if (null != pService) {
path = pService.forString(path);
}
}
//最终创建 Postcard 对象,赋值 Postcard 对象的 path 和 group 字段
return new Postcard(path, group);
}
完了之后是 Postcard 对象调用 navigation() 方法,
//Postcard 的三个重载方法
public Object navigation() {
return navigation(null);
}
public Object navigation(Context context) {
return navigation(context, null);
}
public Object navigation(Context context, NavigationCallback callback) {
return ARouter.getInstance().navigation(context, this, -1, callback);
}
//ARouter
public Object navigation(Context mContext, Postcard postcard, int requestCode, NavigationCallback callback) {
return _ARouter.getInstance().navigation(mContext, postcard, requestCode, callback);
}
//_ARouter
protected Object navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
try {
//核心部分
LogisticsCenter.completion(postcard);
}
//这里如果需要回调就要在入参里传入 NavigationCallback对象
if (null != callback) {
callback.onFound(postcard);
}
// greenChannel 说是配置是否忽略拦截
//因为默认没有配置拦截器,也没有配置不忽略,所以会进入拦截处理
if (!postcard.isGreenChannel()) { // It must be run in async thread, maybe interceptor cost too mush time made ANR.
//这个 interceptorService 就是初始化的时候 afterInit() 里面做的创建对象
//它的创建过程和页面跳转很像,区别在于 path 不同
//这里先假设它已经创建好了,如果需要拦截处理,应该就是通过 onInterrupt 回调方法处理
//下一步就会调用 _navigation() 方法
interceptorService.doInterceptions(postcard, new InterceptorCallback() {
public void onContinue(Postcard postcard) {
_navigation(context, postcard, requestCode, callback);
}
public void onInterrupt(Throwable exception) {
if (null != callback) {
callback.onInterrupt(postcard);
}
}
}
} else {
return _navigation(context, postcard, requestCode, callback);
}
}
private Object _navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
final Context currentContext = null == context ? mContext : context;
//根据 Postcard 类型会有不同的处理
//我们就只看 activity 的,其他的还有 service, provider, fragment, ContentProvider 等
//这里有个疑问,结合前面的分析,postcard 的 type 值没见它赋值过啊,难道忽略了哪个步骤?
//估计在核心部分 completion 里,不急等下去好好拆开看看
switch (postcard.getType()) {
case ACTIVITY:
//同样以下的 destination,携带的数据,action 等都留着等下分析
final Intent intent = new Intent(currentContext, postcard.getDestination());
intent.putExtras(postcard.getExtras());
int flags = postcard.getFlags();
if (-1 != flags) {
intent.setFlags(flags);
} else if(!(currentContext instanceof Activity)) {
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
}
String action = postcard.getAction();
if (!TextUtils.isEmpty(action)) {
intent.setAction(action);
}
//这个 runInMainThread 方法是它自己写的,内部我看了下就是用到了初始化时创建的那个 Handler 对象,来确保在安卓主线程
runInMainThread(new Runnable() {
public void run() {
//startActivity 也是它自己写的,里面主要兼容了是否需要考虑返回结果,以及 Activity 的切换动画,以及成功跳转后的一个回调。
//所以 Activity 的跳转最终还是系统的方法,不会有别的
startActivity(requestCode, currentContext, intent, postcard, callback);
}
});
}
}
Completion 来看下核心部分
public synchronized static void completion(Postcard postcard) {
//从 Warehouse.routes 里取出路径相关联的 RouteMeta 对象
//从前面初始化过程知道,最先执行这里的是创建 interceptorService 的时候,
//interceptorService 的路径是 /arouter/service/interceptor
//一开始肯定找不到,routeMeta 为 null
//----------------------------------------------------------------
//递归调用回来,这时 Warehouse.routes 里就有 /arouter/service/interceptor 这个路径对应的 RouteMeta 对象了,即 InterceptorServiceImpl 对象,这就能看前面拦截器里面所做的逻辑细节了。
RouteMeta routeMeta = Warehouse.routes.get(postcard.getPath());
if (null == routeMeta) {
//interceptorService 的 group 的值为 arouter,这在前面初始化的时候存进去了
//所以得到的 groupMeta 对象就是 ARouter$$Group$$arouter 类型
Class extends IRouteGroup> groupMeta = Warehouse.groupsIndex.get(postcard.getGroup());
IRouteGroup iGroupInstance = groupMeta.getConstructor().newInstance();
//在这里就会加载 /arouter/service/autowired, /arouter/service/interceptor 两个路径及对应的数据
iGroupInstance.loadInto(Warehouse.routes);
//接着在 group 组里移除了 arouter
Warehouse.groupsIndex.remove(postcard.getGroup());
//递归调用
completion(postcard);
} else {
//这里一顿赋值,像 interceptorService 在刚才 loadInto 的时候做了初始化
postcard.setDestination(routeMeta.getDestination());
postcard.setType(routeMeta.getType());
postcard.setPriority(routeMeta.getPriority());
postcard.setExtra(routeMeta.getExtra());
//对 uri 做了单独处理
Uri rawUri = postcard.getUri();
if (null != rawUri) {
Map resultMap = TextUtils.splitQueryParameters(rawUri);
Map paramsType = routeMeta.getParamsType();
if (MapUtils.isNotEmpty(paramsType)) {
for (Map.Entry params : paramsType.entrySet()) {
setValue(postcard,
params.getValue(),
params.getKey(),
resultMap.get(params.getKey()));
}
postcard.getExtras().putStringArray(ARouter.AUTO_INJECT, paramsType.keySet().toArray(new String[]{}));
}
postcard.withString(ARouter.RAW_URI, rawUri.toString());
}
//下面的代码逻辑和 activity 不相关就不看了
}
}
以上是拿 interceptorService 做的分析,如果是 Activity 应该是同理的,
首先一个 group 对应一组路由(即 Warehouse.routes),刚开始 Activity 的 path 肯定也没有对应的 RouteMeta 于是找到对应 IRouteGroup 对象,通过它去 load 路由列表。不同点在于,ARouterGrouparouter 类是已经在依赖包里的,而用于 Activity 跳转的这些 IRouteGroup 类应该是通过 @Route 注解编译生成的,大概看个长相,
/**
* DO NOT EDIT THIS FILE!!! IT WAS GENERATED BY AROUTER. */
//长的就像这样,不可编辑,因为是框架生成的
public class ARouter$$Group$$login implements IRouteGroup {
@Override
public void loadInto(Map atlas) {
atlas.put("/login/fastway/login", RouteMeta.build(RouteType.ACTIVITY, FastLoginActivity.class, "/login/fastway/login", "login", null, -1, -2147483648));
}
}
这样一来,同一个 group 下的路径都被加载了。这也看出同组间的页面跳转会比不同组之间的页面跳转省事很多。
加载了路由列表后,接下去递归的逻辑也和 interceptorService 类似。到这就能解释前面 postcard 的 getType 等值是怎么来的了。
最后,再来回顾一遍整体逻辑。首先添加依赖进行相关 gradle 配置,接着定义好页面路由路径,配置初始化。框架的加载逻辑是,先通过初始化过程完成路由表组的加载(就是根据 @Route 以及框架预先定义的组进行加载),然后在真正要进行页面跳转时,从缓存路由列表里获取路由信息,找不到则通过事先加载的路由表组进行加载路由列表,找得到则继续组装具体页面相关的一些信息,最后通过系统完成页面跳转。
题外话
源码这么看下来,发现 ARouter 和 _ARouter 的关系有点像 ContextWrapper 和 mBase 还有 PhoneWindow 和 mDecor 的关系,一个对外,一个对内。看似工作是 ARouter 做的,实际上都是 _ARouter 做的。
我觉得多看源码的好处就是可以模仿源码的代码实现,慢慢的逼格就上去了。