安卓进阶二: 这次我把ARouter源码搞清楚啦!
随着面试和工作中多次遇到ARouter的使用问题,我决定把ARouter的源码从头到尾理一遍。 让我瞧瞧你到底有几斤几两,为啥大家在项目组件化中都用你做路由框架。
前言
在开发一个项目的时候,我们总是希望架构出的代码能够自由复用,**自由组装。**实现业务模块的范围的单一职责。并且抽离出各种各样可重复使用的组件。 而在组件化过程中,路由是个绕不过去的坎。 当模块可以自由拼装拆除的时候,类的强引用方式变得不可取。因为有些类很可能在编译期间就找不到了。所以就需要有种方式能通直接过序列化的字符串来拉起对应的功能或者页面。也就是通常的路由功能。 ARouter也是接受度比较高的一个开源路由方案。 于是我写了这篇文章,对ARouter的源码原理进行一个全面的分析梳理。 看完文章过后,将能学习到Arouter 的使用原理,注解处理器的开发方式,gradle插件如何对于和class文件转dex进行中间处理。
名词介绍
apt:
APT(Annotation Processing Tool)即注解处理器,是一种处理注解的工具,确切的说它是javac的一个工具,它用来在编译时扫描和处理注解。注解处理器以Java代码(或者编译过的字节码)作为输入,生成**.java文件**作为输出。ARouter
中通过处理注解生成相关路由信息类 asm:
ASM 库是一款基于 Java 字节码层面的代码分析和修改工具。ASM 可以直接生产二进制的 class 文件,也可以在类被加载入 JVM 之前动态修改类行为。在ARouter中用于arouter_register
插件插入初始化代码。官方链接
目录
- 项目模块结构
- ARouter路由使用分析
- ARouter初始化分析
- ARouter注解处理代码生成:arouter-compiler
- ARouter自动注册插件:arouter-register
- ARouter idea 插件:arouter helper
- 自动代码生成
- gradle插件
一. 项目模块结构
官方仓库 我们克隆github的ARouter源码,打开项目就是如下的项目结构图。
模块 | 说明 |
---|---|
app | 示例app模块 |
module-java | java示例组件模块 |
module-java-export | java实例模块的服务数据模块,定义了一个示例的IProvider 服务接口和一些实体类 |
module-kotlin | kotlin示例组件模块 |
arouter-annotation | 注解类以及相关数据实体类 |
arouter-api | 主要api模块,提供ARouter类等路由方法 |
arouter-compiler | 处理注解,生成相关代码 |
arouter-gradle-plugin | gradle插件,jar包中添加自动注册代码,减少扫描dex文件开销 |
arouter-idea-plugin | idea插件, |
重点类简介 |
ARouter
:api入口类LogisticsCenter
:路由逻辑中心维护所有路由图谱Warehouse
:保存所有路由映射,通过他找到所以字符串对应的路由信息。这些信息都是从解析注解标记自动生成。//todo 可能不正确RouteType
:路由类型,现在有:*ACTIVITY,SERVICE,PROVIDER,CONTENT_PROVIDER,BOARDCAST,METHOD,FRAGMENT,UNKNOWN*
RouteMeta
:路由信息类,包含路由类型,路由目标类class,路由组group名等。
二. ARouter 路由使用分析
ARouter的接入和使用参考官方说明就可以了。
接下来从常用Activity 跳转入手来了解路由导航处理
从最常用的api入手,我们就能知道ARouter 最主要的运转原理,了解他是怎么支撑实现跳转这个我们最常用的功能的。 跳转Activity代码如下:
ARouter.getInstance().build("/test/activity").navigation();
这一句代码就完成了activity跳转
要点步骤如下:
- 通过
PathReplaceService
预处理路径,并从path:"/test/activity"
中抽取出group: "test"
- 将
path
和group
作为参数创建Postcard
实例 - 调用 postcard#navigation ,最终导航到_ARouter#navigation
- 通过 group 和 path 从
Warehouse.``*routes*
获取具体路径信息RouteMeta,完善postcard。
详细说明
前面提到的一些用户自定义的 Service
第一步抽取 group
而不管是跳转Activity,获取Fragment还是获取Provider。 ARouter.getInstance().build("/test/activity")
是 ARouter最核心的路由api。而这个build出来的,是Postcard
类。 我们先看build代码的执行路径:
protected Postcard build(String path) {
if (TextUtils.isEmpty(path)) {
throw new HandlerException(Consts.TAG + "Parameter is invalid!");
} else {
/// 用户自定义路径处理类。默认为空。 ARouter.getInstance().navigation 直接获取Provider后文分析
PathReplaceService pService = ARouter.getInstance().navigation(PathReplaceService.class);
if (null != pService) {
path = pService.forString(path);
}
///获取path中包含的 group 作为参数二
return build(path, extractGroup(path), true);
}
}
代码中可以看到,其中通过ARouter.``*getInstance*``().navigation(PathReplaceService.class)
获取路径替换类,对路径进行了预处理操作(默认没有自定义实现类)。通过extractGroup方法从 path中获取了 group信息。
private String extractGroup(String path) {
if (TextUtils.isEmpty(path) || !path.startsWith("/")) {
throw new HandlerException(Consts.TAG + "Extract the default group failed, the path must be start with '/' and contain more than 2 '/'!");
}
try {
String defaultGroup = path.substring(1, path.indexOf("/", 1));
if (TextUtils.isEmpty(defaultGroup)) {
throw new HandlerException(Consts.TAG + "Extract the default group failed! There's nothing between 2 '/'!");
} else {
return defaultGroup;
}
} catch (Exception e) {
logger.warning(Consts.TAG, "Failed to extract default group! " + e.getMessage());
return null;
}
}
从extractGroup
源码可知,抽取group的时候对路由路径 "/test/activity"
做了校验:
- 一定要"/" 开头
- 至少要有两个"/"
- 第一个反斜杠后面的就是group
所以path路径一定要是类似 的格式,或者多来几个"/"。
第二步:创建Postcard实例
很简单,直接new出来了
return new Postcard(path, group);
第三步:调用_ARouter#navigation
这块代码是路由的安卓核心跳转代码 很长一大串:
protected Object navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
///1.自定义预处理代码
PretreatmentService pretreatmentService = ARouter.getInstance().navigation(PretreatmentService.class);
if (null != pretreatmentService && !pretreatmentService.onPretreatment(context, postcard)) {
// 预处理拦截了 返回
return null;
}
// 设置context
postcard.setContext(null == context ? mContext : context);
try {
///2.通过路由信息,找到对应的路由信息 RouteMeta ,根据路由类型 RouteType
///完善posrcard
LogisticsCenter.completion(postcard);
} catch (NoRouteFoundException ex) {
///... 省略异常日志和弹窗展示。以及相关回调方法
///值得一提的是走了 DegradeService 的自定义丢失回调
}
if (null != callback) {
callback.onFound(postcard);
}
///3.如果不是绿色通道,需要走拦截器:InterceptorServiceImpl
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) {
///4.继续导航方法
_navigation(postcard, requestCode, callback);
}
@Override
public void onInterrupt(Throwable exception) {
///省略拦截后的一些代码
}
});
} else {
///4.继续导航方法
return _navigation(postcard, requestCode, callback);
}
return null;
}
简单总结一下主要代码步骤:
- 如有自定义预处理导航逻辑,执行并检查拦截
- 通过path路径找到对应的routemeta路由信息,用该信息完善
postcard
对象(LogisticsCenter.completion
方法中完成,细节后文分析) - 如果不是绿色通道,需要走拦截器:
InterceptorServiceImpl
。该拦截器服务类中完成拦截器一一执行。(2的源码细节可知,PROVIDER
和FRAGMENT
类型是绿色通道) - 继续导航方法,调用_navigation。
看代码:
private Object _navigation(final Postcard postcard, final int requestCode, final NavigationCallback callback) {
final Context currentContext = postcard.getContext();
switch (postcard.getType()) {
case ACTIVITY:
// Build intent
final Intent intent = new Intent(currentContext, postcard.getDestination());
//...省略完善intent代码
// 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;
}
很明显,代码中注意对各种类型的路由做了处理。
- *ACTIVITY:*新建
Intent
,通过postcard
信息,完善intent
走context.startActivity
或者context.startActivityForResult
。 - PROVIDER:
postcard.getProvider()
获取provider
实例(实例化代码在LogisticsCenter.completion
) - FRAGMENT,BOARDCAST,CONTENT_PROVIDER:
routeMeta.getConstructor().newInstance()
通过路由信息实例化出实例,如果是Fragment的话,则另外再设置extras
信息。 - METHOD,SERVICE:返回空,啥也不做。说明该类型路由调用
*navigation*
没啥意义。
看到这里,对于Activity 的路由跳转就很直观了,就是调用了startActivity
或者 startActivityForResult
方法,其他provider
fragment
等实例的获取也十分得清晰明了了,接下来讲讲上面提到的补全postcard
关键代码。
关键代码:LogisticsCenter.completion 分析
完善postcard
信息代码是通过LogisticsCenter.completion
方法完成的。现在来梳理一下这一块代码:
/**
* 通过RouteMate 完善 postcard
* @param postcard Incomplete postcard, should complete by this method.
*/
public synchronized static void completion(Postcard postcard) {
//省略空判断
RouteMeta routeMeta = Warehouse.routes.get(postcard.getPath());
if (null == routeMeta) {
// 如果路由的组group没有找到,直接抛异常
if (!Warehouse.groupsIndex.containsKey(postcard.getGroup())) {
throw new NoRouteFoundException(TAG + "There is no route match the path [" + postcard.getPath() + "], in group [" + postcard.getGroup() + "]");
} else {
//...省略一些日志代码
// 1.动态添加组元素(从groupsIndex 中找到对应 IRouteGroup的生成类,再对组元素进行加载)
addRouteGroupDynamic(postcard.getGroup(), null);
completion(postcard); // Reload
}
} else {
postcard.setDestination(routeMeta.getDestination());
postcard.setType(routeMeta.getType());
postcard.setPriority(routeMeta.getPriority());
postcard.setExtra(routeMeta.getExtra());
Uri rawUri = postcard.getUri();
///2.如果有uri 信息,解析uri相关参数。解析出AutoWired的参数的值
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());
}
///3.获取provider实例,如果初始获取,初始化该provider, 最后赋值给postcard
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) {
logger.error(TAG, "Init provider failed!", e);
throw new HandlerException("Init provider failed!");
}
}
postcard.setProvider(instance);
postcard.greenChannel(); // Provider should skip all of interceptors
break;
case FRAGMENT:
postcard.greenChannel(); // Fragment needn't interceptors
default:
break;
}
}
}
梳理一下这一块的代码,这一部分代码完善了postcard信息,总共分成了三个要点
- **获取路由信息:**如果路由信息找不到,通过组信息,重新动态添加组group内所有路由 ,调用
*addRouteGroupDynamic*
。 - **获取uri内的参数:**如果postcard创建的时候有传递uri。解析uri里面所有需要AutoInject的参数。放置到postcard中。
- **获取Provider实例,配置是否不走拦截器的绿色通道:**不存在的Provider通过路由信息的 getDestination 反射创建实例并初始化,存在的直接获取。
分析到这里。各种RouteType
的跳转,实例获取都已经明了了。 现在剩下的问题是,WareHouse
里面的路由信息数据是哪里来的?前面提到了动态添加组内路由的方法*addRouteGroupDynamic*
。我们来看看:
public synchronized static void addRouteGroupDynamic(String groupName, IRouteGroup group) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
if (Warehouse.groupsIndex.containsKey(groupName)){
// If this group is included, but it has not been loaded
// load this group first, because dynamic route has high priority.
Warehouse.groupsIndex.get(groupName).getConstructor().newInstance().loadInto(Warehouse.routes);
Warehouse.groupsIndex.remove(groupName);
}
// cover old group.
if (null != group) {
group.loadInto(Warehouse.routes);
}
}
可以看到Warehouse.routes
里面的所有路由信息,都是从 IRouteGroup.loadInto
加载出来的。而IRouteGroup
都存在Warehouse.``*groupsIndex*
中。 这时候新的问题出现了,Warehouse.``*groupsIndex*
的数据是哪里来的呢? 下一节 ARouter 初始化分析
就有答案。
tips:提到的对外可自定义配置:
简单罗列下源码中提到的可自定义配置的IProvider。便于使用的时候自定义。
- PathReplaceService ///路由自定义处理替换
- DegradeService //没有找到路由的通用回调
- PretreatmentService ///navigation 预处理拦截
通过Class 获取IProvider实例
前面提到的PathReplaceService
等用户自定义类,都是通过ARouter.getInstance().navigation(clazz)
方式获取的。 这一块代码又是怎么从路由信息中获取到实例的呢?看看具体的navigation代码:
protected <T> T navigation(Class<? extends T> service) {
try {
//1.通过类名从Provider路由信息索引中,获取路由信息,组建postcart
Postcard postcard = LogisticsCenter.buildProvider(service.getName());
// Compatible 1.0.5 compiler sdk.
// Earlier versions did not use the fully qualified name to get the service
if (null == postcard) {
// No service, or this service in old version.
//1.通过类名从Provider路由信息索引中,获取路由信息,组建postcart
postcard = LogisticsCenter.buildProvider(service.getSimpleName());
}
if (null == postcard) {
return null;
}
// Set application to postcard.
postcard.setContext(mContext);
//2.完善postcard ,该方法里面创建provider
LogisticsCenter.completion(postcard);
return (T) postcard.getProvider();
} catch (NoRouteFoundException ex) {
logger.warning(Consts.TAG, ex.getMessage());
return null;
}
}
很明显,主要代码就是 LogisticsCenter.``*buildProvider*``(service.getName())
,获取到了postcard
。后面完善postcard
和 获取provider
实例的代码都已经在上文讲过。 所以我们就看*buildProvider*
方法:
public static Postcard buildProvider(String serviceName) {
RouteMeta meta = Warehouse.providersIndex.get(serviceName);
if (null == meta) {
return null;
} else {
return new Postcard(meta.getPath(), meta.getGroup());
}
}
和路由组信息获取类似,Provider
的路由信息从 Warehouse.``*providersIndex*
维护的映射表中获取。 所以*providersIndex*
是专门用来给没有@Route 路由信息的Provider创建实例用的。这就是维护*providersIndex*
的用途。 接下来的问题就转为了 *providersIndex*
里面的数据是哪里来的。
小结
路由跳转以及获取Provider等实例的原理可以简单总结下:
- 先是获取
postcard
,可能是直接通过路由路径和uri
构建, 如"/test/activity1"
,也可能是通过Provider
类名从索引获取,如PathReplaceService.class.getName()
- 然后通过
RouteMate
完善postcard
。获取诸如类名信息,路由类型,provider实例等信息。 - 最后导航,根据路由类型作出跳转或者返回对应实例。
关键点在于WareHouse
维护的路由图谱。
三. ARouter初始化分析
我们看下对用户提供的ARouter#init
方法:
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
- 获取拦截器服务实例初始化所有拦截器
初始化ARouter
init代码最终调用到了LogisticsCenter#init
通过代码我们了解到了这么几个过程:
- 方式一 :
ARouter-auto-register
插件加载路由表(如果有该插件),该方式详细分析见第五节。 - 方式二 :
- 在需要的时候扫描所有 dex文件,找到所有包名为
com.alibaba.android.ARouter.routes
的类,类名放到routerMap
集里面。 - 实例化上面找到的所有类,并通过这些集类加载对应的集映射索引到WareHouse中。
很显然,com.alibaba.android.ARouter.routes
包名下面的类都是自动生成的路由表类。 通过搜索我们能找到样例代码中生成的该包名对象们: module_java
生成的IRouteRoot
代码如下所示
public class ARouter$$Root$$modulejava implements IRouteRoot {
@Override
public void loadInto(Map<String, Class<? extends IRouteGroup>> routes) {
routes.put("m2", ARouter$$Group$$m2.class);
routes.put("module", ARouter$$Group$$module.class);
routes.put("test", ARouter$$Group$$test.class);
routes.put("yourservicegroupname", ARouter$$Group$$yourservicegroupname.class);
}
}
这样我们就完全清楚了 WareHouse
里面维护的所有路由信息是哪里来的了。追本溯源。接下来我们只需要知道 ARouter$$Root$$modulejava
等类,是啥时候怎么生成的。我们在下面一小节进行分析。初始化ARouter的过程,其实就是填充Warehouse
的*providersIndex*
,*groupsIndex*
,*interceptorsIndex*
初始化后续:初始化所有拦截器
初始化完成,看看初始化完成后的操作afterInit
static void afterInit() {
// Trigger interceptor init, use byName.
interceptorService = (InterceptorService) ARouter.getInstance().build("/ARouter/service/interceptor").navigation();
}
这一块代码就是navigation
获取到了InterceptorService
。上面也讲过,在执行navigation
的时候,会调用IProvider
的init
方法。所以我们需要找到InterceptorService
的实现类,并看看他的init
做了什么。项目中其实现类是InterceptorServiceImpl
,找到init
代码如下:
@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();
}
}
}
});
}
代码很明白的告诉我们,该初始化代码从拦截器路由信息索引里面加载并实例化了所有拦截器。然后通知等待的拦截器开始拦截。
小结
看完初始化代码之后,明白了WareHouse的数据来源,现在问题变成了com.alibaba.android.ARouter.routes
包名的代码何时生成。我们且看下回分解。