文章目录
- 前言
- 一、ARouter 简介
- 二、ARouter 使用
- 1.添加依赖和配置
- 2.添加注解
- 3.初始化SDK
- 4.发起路由操作
- 三、ARouter 成员
- 1. PostCard 明信片
- 2. Interceptor 拦截器
- 3. Warehouse 路由仓库
- 4. ARouter 注解处理
- 四、ARouter 原理
- 五、ARouter 源码分析
- 1. ARouter 初始化
- 1.1 ARouter#init()
- 1.2 _ARouter#init()
- 1.3 LogisticsCenter#init()
- 1.4 ClassUtils#getFileNameByPackageName()
- 2. ARouter 路由跳转
- 2.1 ARouter.getInstance()
- 2.2 ARouter#build()
- 2.3 _ARouter#build()
- 2.3 _ARouter#navigation()
- 2.3.1 LogisticsCenter#completion()
- 2.3.2 _ARouter#_navigation()
- 3. ARouter 获取 Service
- 总结
- 参考
前言
在日常开发中,随着项目业务越来越复杂,项目中的代码量也越来越多,如何维护、扩展、解耦等成了一个非常头疼问题。为解决此问题而衍生出的诸如:插件化、组件化、模块化等热门技术。 使用组件化来改造项目时的难点,就是实现各个组件之间的通讯,通常解决方案采用路由中间件,来实现页面之间的跳转关系。本文要解析的 ARouter 路由框架就是众多解决方案中比较优秀的一个开源库,并且是国人团队开发的,所以中文文档非常详细,以便使用者快速接入。
一、ARouter 简介
ARouter 是阿里开源的一款用于帮助 Android App 进行组件化改造的路由框架,支持模块间的路由、通信、解耦,是 Android 平台中对页面、服务提供路由功能的中间件,以实现在不同模块的 Activity 之间跳转。
ARouter 功能介绍:
- 支持直接解析标准URL进行跳转,并自动注入参数到目标页面中
- 支持多模块工程使用
- 支持添加多个拦截器,自定义拦截顺序
- 支持依赖注入,可单独作为依赖注入框架使用
- 支持InstantRun
- 支持MultiDex(Google方案)
- 映射关系按组分类、多级管理,按需初始化
- 支持用户指定全局降级与局部降级策略
- 页面、拦截器、服务等组件均自动注册到框架
- 支持多种方式配置转场动画
- 支持获取 Fragment
- 完全支持 Kotlin 以及混编
- 支持第三方 App 加固(使用 arouter-register 实现自动注册)
- 支持生成路由文档
- 提供 IDE 插件便捷的关联路径和目标类
- 支持增量编译(开启文档生成后无法增量编译)
- 支持动态注册路由信息
ARouter 的典型应用场景有:
- 从外部URL映射到内部页面,以及参数传递与解析
- 跨模块页面跳转,模块间解耦
- 拦截跳转过程,处理登陆、埋点等逻辑
- 跨模块API调用,通过控制反转来做组件解耦
ARouter 工作原理:
ARouter 架构:
- app:ARouter 提供的一个测试 Demo;
- arouter-annotation:模块中声明了很多注解信息和一些枚举类
- arouter-api:ARouter 的核心 api,转换过程的核心操作都在这个模块里面
- arouter-compiler:APT 处理器,用来自动生成路由表
- arouter-gradle-plugin:编译期使用的 Plugin 插件,主要作用是用于编译器自动加载路由表,节省应用的启动时间
二、ARouter 使用
1.添加依赖和配置
android {
defaultConfig {
...
javaCompileOptions {
annotationProcessorOptions {
// ARouter参数
arguments = [AROUTER_MODULE_NAME: project.getName()]
}
}
}
}
dependencies {
// 替换成最新版本, 需要注意的是 api 要与 compiler 匹配使用,均使用最新版可以保证兼容
compile 'com.alibaba:arouter-api:1.2.4'
annotationProcessor 'com.alibaba:arouter-compiler:1.2.4'
...
}
2.添加注解
// 在支持路由的页面上添加注解(必选)
// 这里的路径需要注意的是至少需要有两级:/xx/xx
@Route(path = "/first/activity")
public class FirstActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_first);
......
}
}
3.初始化SDK
if (BuildConfig.DEBUG) { // 这两行必须写在init之前,否则这些配置在init过程中将无效
ARouter.openLog(); // 打印日志
ARouter.openDebug(); // 开启调试模式(如果在InstantRun模式下运行,必须开启调试模式!线上版本需要关闭,否则有安全风险)
}
ARouter.init(mApplication); // 尽可能早,推荐在Application中初始化
4.发起路由操作
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
@Override
public void onClick(View v) {
if (v.getId() == R.id.first_button) {
// 1. 应用内简单的跳转
ARouter.getInstance().build("/first/activity").navigation();
// 2. 跳转并携带参数
ARouter.getInstance().build("/test/1")
.withLong("key1", 666L)
.withString("key3", "888")
.withObject("key4", new Test("Jack", "Rose"))
.navigation();
}
}
}
三、ARouter 成员
1. PostCard 明信片
public final class Postcard extends RouteMeta {
// Base
private Uri uri; // 使用 Uri 方式发起路由
private Object tag; // A tag prepare for some thing wrong. inner params, DO NOT USE!
private Bundle mBundle; // 需要传递的参数使用 Bundle 存储
private int flags = 0; // 启动 Activity 的标志,如:NEW_FALG
private int timeout = 300; // 路由超时时间
private IProvider provider; // 使用 IProvider 的方式跳转
private boolean greenChannel; // 绿色通道,可以不经过拦截器
// 序列化服务serializationService:需要传递Object自定义类型对象,就需要实现这个服务
private SerializationService serializationService;
private Context context; // 在使用应用程序或活动之前,需检查实例类型
private String action; // Activity 跳转的 Action
// Animation
private Bundle optionsCompat; // Activity 的过渡动画
private int enterAnim = -1;
private int exitAnim = -1;
......
}
Postcard 明信片,跟我们去各大旅游景点购买然后寄给朋友的明信片是一样的,包含需要传递的参数、跳转方式等等,PostCard 继承自 RouteMeta,来看看其包含哪些重要参数:
public class RouteMeta {
private RouteType type; // 路由类型:如Activity,Fragment,Provider等
private Element rawType; // 路由原始类型,在编译时用来判断
private Class<?> destination; // 目标 Class 对象
private String path; // 路由注册的 path
private String group; // 路由注册的 group 分组
private int priority = -1; // 路由执行优先级,priority 越低,优先级越高,这个一般在拦截器中使用
private int extra; // 额外数据
private Map<String, Integer> paramsType; // 参数类型,例如 Activity 中使用 @Autowired 的参数类型
private String name; // 路由名字,用于生成 javadoc // 参数配置(对应paramsType).
private Map<String, Autowired> injectConfig; // 缓存注入配置(对应paramsType)
......
}
RouteMeta 主要存储的是一些目标对象的信息,这些对象是在路由注册的时候才会生成。
2. Interceptor 拦截器
ARouter 中存在一套拦截器机制,所有的路由调用在 completion 的过程中都需经过自定义的一系列拦截器,实现一些 AOP 切面编程。因此先来看看其拦截器机制的实现,首先看一下 IInterceptor 接口类:
public interface IInterceptor extends IProvider {
/**
* The operation of this interceptor.
*
* @param postcard meta
* @param callback cb
*/
void process(Postcard postcard, InterceptorCallback callback);
}
IInterceptor 继承了 IProvider,所以其也是一个服务类型,只需要实现 process() 方法就可以实现拦截操作,在其内部对 Postcard 进行处理,拦截器的执行是通过 InterceptorServiceImpl#doInterceptions() 方法实现的:
@Route(path = "/arouter/service/interceptor")
public class InterceptorServiceImpl implements InterceptorService {
private static boolean interceptorHasInit;
private static final Object interceptorInitLock = new Object();
@Override
public void doInterceptions(final Postcard postcard, final InterceptorCallback callback) {
if (MapUtils.isNotEmpty(Warehouse.interceptorsIndex)) {
// 检查拦截器 IInterceptor 的初始化状态,如还未初始化完成,则等待 10s,如仍未初始化完成则报错
checkInterceptorsInitStatus();
if (!interceptorHasInit) { // 回调通知拦截器初始化耗时太多异常
callback.onInterrupt(new HandlerException("Interceptors initialization takes too much time."));
return;
}
LogisticsCenter.executor.execute(new Runnable() {
@Override
public void run() {
// 构建值为 interceptors 个数的 CountDownLatch 计数器,用于倒数计数,子线程每执行成功一个
// 调用 CountDownLatch.countDown() 方法,interceptors 的个数减 1,直到 interceptors 的个数减为 0
// CountDownLatch.await() 就会自动解除等待状态, 不再阻塞主线程, 进入运行状态
CancelableCountDownLatch interceptorCounter = new CancelableCountDownLatch(Warehouse.interceptors.size());
try {
// 调用 _execute() 方法执行一个拦截器的 iInterceptor.process() 方法
_execute(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((Throwable) postcard.getTag());
} else {
callback.onContinue(postcard);
}
} catch (Exception e) {
callback.onInterrupt(e);
}
}
});
} else {
callback.onContinue(postcard);
}
}
/**
* Excute interceptor
*
* @param index current interceptor index
* @param counter interceptor counter
* @param postcard routeMeta
*/
private static void _execute(final int index, final CancelableCountDownLatch counter, final Postcard postcard) {
if (index < Warehouse.interceptors.size()) {
// 获取 index 对应的拦截器 IInterceptor
IInterceptor iInterceptor = Warehouse.interceptors.get(index);
// 调用拦截器 IInterceptor#process() 方法执行拦截操作,对 Postcard 进行处理
iInterceptor.process(postcard, new InterceptorCallback() {
@Override
public void onContinue(Postcard postcard) {
// Last interceptor excute over with no exception.
counter.countDown(); // 计数器 CountDownLatch 的 interceptors 的个数减 1
// 继续调用 _execute() 方法,只是 index 的数值 + 1,来执行下一个拦截器的 iInterceptor.process() 方法
_execute(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 execute over with fatal exception.
// save the exception message for backup.
postcard.setTag(null == exception ? new HandlerException("No message.") : exception);
counter.cancel();
}
});
}
}
......
}
InterceptorServiceImpl#doInterceptions() 方法中,通过 ThreadPoolExecutor 线程池和 CountDownLatch 计数器,实现对每个拦截器 IInterceptor 调用其 process() 方法执行拦截操作,对 Postcard 进行处理。
3. Warehouse 路由仓库
Warehouse 意为仓库,用于存放被 @Route、@Interceptor 注释的路由相关的信息,也就是我们关注的 destination 等信息。
class Warehouse {
// 保存所有 IRouteGroup 实现类的class对象,在 ARouter 初始化中赋值,key 是 path 第一级
//(IRouteGroup 实现类是编译时生成,代表一个组,即path第一级相同的所有路由,包括Activity和Provider服务)
static Map<String, Class<? extends IRouteGroup>> groupsIndex = new HashMap<>();
// 保存所有路由元信息 RouteMeta,是在 completion 中赋值,key是path
// 首次进行某个路由时就会加载整个group的路由,即IRouteGroup实现类中所有路由信息。包括Activity和Provider服务
static Map<String, RouteMeta> routes = new HashMap<>();
// 保存所有服务provider实例,在completion中赋值,key是IProvider实现类的class
static Map<Class, IProvider> providers = new HashMap<>();
// 保存所有provider服务的元信息(实现类的class对象),在 ARouter 初始化中赋值,key是IProvider实现类的全类名
// 主要用于使用IProvider实现类的class发起的获取服务的路由,例如ARouter.getInstance().navigation(HelloService.class)
static Map<String, RouteMeta> providersIndex = new HashMap<>();
// 保存所有拦截器实现类的class对象,在 ARouter 初始化时收集到,key是优先级
static Map<Integer, Class<? extends IInterceptor>> interceptorsIndex = new UniqueKeyTreeMap<>("More than one interceptors use same priority [%s]");
// 保存所有拦截器,在 ARouter 初始化完成后立即创建
static List<IInterceptor> interceptors = new ArrayList<>();
......
}
其中 groupsIndex、providersIndex、interceptorsIndex 是 ARouter 初始化时就准备好的基础信息,为业务中随时发起路由操作(Activity 跳转、服务获取、拦截器处理)做好准备。
4. ARouter 注解处理
ARouter 通过注解处理器 AnnotationProcessor 配合 AutoService 来实现的,并通过 JavaPoet 实现对 Java 文件的编写。
AnnotationProcessor:注解处理器是一种工具,它通过检索源代码中的注解信息,执行特定的代码生成任务或对代码进行检查。ARouter 使用注解处理器在源码编译的阶段,通过 APT 获取被 @Route、@Interceptor 等注解的路由相关信息,在对应模块下动态下动态生成 .java 源文件,通常是自动产生一些有规律性的重复代码,解决了手工编写重复代码的问题,大大提升编码效率。详细可以看看 arouter-complier 包下的具体实现。
AutoService:是 Google 开发的一个自动生成 SPI(全称是 Service Provider Interface) 清单文件的框架,用来自动帮我们注册 APT 文件(全称是 Annotation Process Tool,或者叫注解处理器,AbstractProcessor 的实现类)。
JavaPoet:Java 诗人,名字是不是很优雅,JavaPoet 是由 Square 推出的开源 Java 代码生成框架,能够提供 Java 生成源代码文件的能力,通过这种自动化生成代码的方式,可以让我们用更加简洁优雅的方式替代掉繁琐冗杂的重复工作。
SPI:Service Provider Interface 的简称,是 JDK 默认提供的一种将接口和实现类进行分离的机制。这种机制能将接口和实现进行解耦,大大提升系统的可扩展性。
SPI 机制约定:当一个 Jar 包需要提供一个接口的实现类时,该 Jar 包需要在 META-INF/services 目录里同时创建一个以服务接口命名的文件,该文件里就是实现该服务接口的具体实现类。而当外部程序装配这个模块的时候,就能通过该 Jar 包 META-INF/services/ 里的配置文件找到具体的实现类名,并装载实例化,完成模块的注入。
四、ARouter 原理
ARouter 工作原理主要包括:路由表生成、路由匹配和页面跳转三个方面:
- ARouter 通过注解处理器来生成路由表,在每个组件的 build.gradle 文件中,配置注解处理器的依赖和配置信息,在编译时,注解处理器会扫描项目中所有使用了 @Route 注解的类,然后根据注解中的信息生成一个路由表。该路由表包含了每个被注解标记的类对应的路由信息,如:路径、组件名、优先级等;
- ARouter 在运行时会根据路由表来进行路由匹配,当需要跳转到某个页面时,通过调用 ARouter.getInstance().build(path) 方法来获取一个路由构建器,构建器中的 path 参数对应了要跳转的页面路径。随后 ARouter 根据该路径在路由表中进行匹配,找到对应的 Class 对象并返回;
- ARouter 通过反射来进行页面跳转,当找到目标 Class 对象后,调用 navigation() 方法进行页面跳转,ARouter 自动调用目标页面的构造方法来创建一个实例,并且会根据传递的参数来进行参数的注入(ARouter.withString() 方法)。 然后 navigation() 方法的内部调用 startActivity(intent) 方法进行页面跳转,至此便实现两个相互没有依赖的 module 顺利的启动对方的 Activity 的目标。
五、ARouter 源码分析
1. ARouter 初始化
ARouter 在使用前需要通过调用 ARouter#init() 方法并传入 Application 进行初始化:
1.1 ARouter#init()
public final class ARouter {
private volatile static ARouter instance = null;
private volatile static boolean hasInit = false;
public static ILogger logger;
/**
* Init, it must be call before used router.
*/
public static void init(Application application) {
if (!hasInit) {
logger = _ARouter.logger;
_ARouter.logger.info(Consts.TAG, "ARouter init start.");
// 继续调用 _ARouter.init() 函数进行初始化
hasInit = _ARouter.init(application);
if (hasInit) {
// ARouter 创建了一个 InterceptorServiceImpl 服务的实例对象,后面讲到拦截器的时候会用到
_ARouter.afterInit();
}
_ARouter.logger.info(Consts.TAG, "ARouter init over.");
}
}
}
继续调用 _ARouter#init() 函数进行初始化:
1.2 _ARouter#init()
final class _ARouter {
static ILogger logger = new DefaultLogger(Consts.TAG);
private volatile static _ARouter instance = null;
private volatile static boolean hasInit = false;
private volatile static ThreadPoolExecutor executor = DefaultPoolExecutor.getInstance();
private static Handler mHandler;
private static Context mContext;
protected static synchronized boolean init(Application application) {
mContext = application;
LogisticsCenter.init(mContext, executor);
logger.info(Consts.TAG, "ARouter init success!");
hasInit = true;
mHandler = new Handler(Looper.getMainLooper());
return true;
}
}
内部初始化并赋值一些 mContext,mHandler 以及字段信息,最重要的是调用 LogisticsCenter#init(mContext, executor) 函数来初始化。
1.3 LogisticsCenter#init()
public class LogisticsCenter {
private static Context mContext;
static ThreadPoolExecutor executor;
private static boolean registerByPlugin;
/**
* LogisticsCenter init, load all metas in memory. Demand initialization
*/
public synchronized static void init(Context context, ThreadPoolExecutor tpe) throws HandlerException {
mContext = context;
executor = tpe;
try {
long startInit = System.currentTimeMillis();
// 首先使用 AGP 插件进行路由表的自动加载
loadRouterMap();
if (registerByPlugin) { // 如果 registerByPlugin 被设置为true,说明使用的是插件加载,直接跳过
logger.info(TAG, "Load router map by arouter-auto-register plugin.");
} else {
// loadRouterMap() 中设置 registerByPlugin 为 false,因此调用下面步骤加载
Set<String> routerMap;
// 如果是 debug 模式或者是新版本的,则每次都会去加载 routerMap,这会是一个耗时操作
if (ARouter.debuggable() || PackageUtils.isNewVersion(context)) {
logger.info(TAG, "Run with debug mode or new install, rebuild router map.");
// 获取 arouter-compiler 模块生成的存储 ClassName 集合的 routerMap
// 根据指定的 packageName 获取 package 下的所有 ClassName
routerMap = ClassUtils.getFileNameByPackageName(mContext, ROUTE_ROOT_PAKCAGE);
if (!routerMap.isEmpty()) {
// 存入 SP 缓存中
context.getSharedPreferences(AROUTER_SP_CACHE_KEY, Context.MODE_PRIVATE).edit().putStringSet(AROUTER_SP_KEY_MAP, routerMap).apply();
}
PackageUtils.updateVersion(context); // 路由表更新完成后保存新版本名
} else {
// 如果是其他的情况,如:release 模式下,已经缓存了 ClassName 列表,则直接去文件中读取
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();
// 遍历 routerMap 获取 ClassName
for (String className : routerMap) {
// 如果className = "com.alibaba.android.arouter.routes.ARouter$$Root"格式
// 则加载类构建对象后通过 loadInto() 方法将路由组信息添加到 Warehouse.groupsIndex 中
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)) {
// 如果className = "com.alibaba.android.arouter.routes.ARouter$$Interceptors"格式
// 则加载类构建对象后通过 loadInto() 方法将拦截器信息添加到 Warehouse.interceptorsIndex 中
((IInterceptorGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.interceptorsIndex);
} else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_PROVIDERS)) {
// 如果className = "com.alibaba.android.arouter.routes.ARouter$$Providers"格式
// 则加载类构建对象后通过 loadInto() 方法将服务 Provider 信息添加到 Warehouse.providersIndex 中
((IProviderGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.providersIndex);
}
}
}
logger.info(TAG, "Load root element finished, cost " + (System.currentTimeMillis() - startInit) + " ms.");
if (Warehouse.groupsIndex.size() == 0) {
logger.error(TAG, "No mapping files were found, check your configuration please!");
}
......
} catch (Exception e) {
throw new HandlerException(TAG + "ARouter init logistics center exception! [" + e.getMessage() + "]");
}
}
}
LogisticsCenter#init() 函数执行流程如下:
- 获取 com.alibaba.android.arouter.routes 下存储 ClassName 的集合 routerMap。如果是 debug 模式或者是新版本的,则每次都会调用 ClassUtils#getFileNameByPackageName() 函数根据指定的 packageName 获取 package 下的所有 ClassName 并保存到集合 routerMap 中;如果不是 debug 模式且之前已经解析过,则直接从 SP 中读取(已有缓存)。注意:debug 模式每次都需要更新,因为类会随着代码的修改而变动。
- 遍历 routerMap 中的 ClassName,如果是 RouteRoot,则加载类构建对象后通过 loadInto() 方法将路由组信息添加到 Warehouse.groupsIndex 中;如果是 InterceptorGroup,则加载类构建对象后通过 loadInto() 方法将拦截器信息添加到 Warehouse.interceptorsIndex 中;如果是 ProviderGroup,则加载类构建对象后通过 loadInto() 方法将服务 Provider 信息添加到 Warehouse.providersIndex 中。
1.4 ClassUtils#getFileNameByPackageName()
public class ClassUtils {
private static final String EXTRACTED_NAME_EXT = ".classes";
private static final String EXTRACTED_SUFFIX = ".zip";
/**
* 通过指定包名,扫描包下面包含的所有的ClassName
*
* @param context U know
* @param packageName 包名
* @return 所有class的集合
*/
public static Set<String> getFileNameByPackageName(Context context, final String packageName) throws PackageManager.NameNotFoundException, IOException, InterruptedException {
final Set<String> classNames = new HashSet<>();
// 通过 getSourcePaths 方法获取 dex 文件 path 集合
List<String> paths = getSourcePaths(context);
// 通过 CountDownLatch 对 path 的遍历处理进行控制
final CountDownLatch parserCtl = new CountDownLatch(paths.size());
// 遍历 path,通过 DefaultPoolExecutor 并发对 path 进行处理
for (final String path : paths) {
DefaultPoolExecutor.getInstance().execute(new Runnable() {
@Override
public void run() {
DexFile dexfile = null; // 加载 path 对应的 dex 文件
try {
if (path.endsWith(EXTRACTED_SUFFIX)) {
// NOT use new DexFile(path), because it will throw "permission error in /data/dalvik-cache"
// 如果是 .zip 结尾的文件通过 DexFile.loadDex 进行加载
dexfile = DexFile.loadDex(path, path + ".tmp", 0);
} else {
// 否则通过 new DexFile 加载
dexfile = new DexFile(path);
}
Enumeration<String> dexEntries = dexfile.entries();
// 遍历 dex 中的 Entry
while (dexEntries.hasMoreElements()) {
// 如果是对应的 package 下的类,则添加到 classNames 中
String className = dexEntries.nextElement();
if (className.startsWith(packageName)) {
classNames.add(className);
}
}
} catch (Throwable ignore) {
Log.e("ARouter", "Scan map file in dex files made error.", ignore);
} finally {
if (null != dexfile) {
try {
dexfile.close();
} catch (Throwable ignore) {
}
}
// 计数器 CountDownLatch 的 paths 的个数减 1
parserCtl.countDown();
}
}
});
}
// 所有 path 处理完成后,CountDownLatch.await() 自动解除等待状态, 不再阻塞主线程, 进入运行状态继续向下走流程
parserCtl.await();
Log.d(Consts.TAG, "Filter " + classNames.size() + " classes by packageName <" + packageName + ">");
return classNames;
}
}
ClassUtils#getFileNameByPackageName() 函数的执行流程如下:
- 通过 getSourcePaths() 方法获取 dex 文件的 path 集合;
- 创建了一个 CountDownLatch 计数器控制 dex 文件的并行处理,以加快速度;
- 遍历 path 列表,通过 DefaultPoolExecutor 线程池对 path 并行处理;
- 加载 path 对应的 dex 文件,并对其内部的 Entry 进行遍历,若发现了对应 package 下的 ClassName,将其加入结果集合 classNames 中。
流程至此,ARouter 初始化过程就完成了对自动生成的路由相关类 RouteRoot、Interceptor、ProviderGroup 的加载,并对它们通过反射构造后将信息加载进了 Warehouse 类中。
2. ARouter 路由跳转
以第二节 ARouter 用法中的发起路由操作的代码为例:
// 1. 应用内简单的跳转
ARouter.getInstance().build("/first/activity").navigation();
首先看一下 ARouter#getInstance() 函数获取 ARouter 实例对象:
2.1 ARouter.getInstance()
public final class ARouter {
private volatile static ARouter instance = null;
private volatile static boolean hasInit = false;
private ARouter() {
}
public static ARouter getInstance() {
if (!hasInit) {
throw new InitException("ARouter::Init::Invoke init(context) first!");
} else {
if (instance == null) {
synchronized (ARouter.class) {
if (instance == null) {
instance = new ARouter();
}
}
}
return instance;
}
}
}
首先检查 ARouter 是否已经初始化,如果已经初始化则直接新建 ARouter 实例对象。
2.2 ARouter#build()
public Postcard build(String path) {
return _ARouter.getInstance().build(path);
}
_ARouter#getInstance() 函数跟 ARouter 的类似,不再继续贴代码,继续转调 _ARouter 的同名 build() 方法:
2.3 _ARouter#build()
final class _ARouter {
/**
* Build postcard by path and default group
*/
protected Postcard build(String path) {
if (TextUtils.isEmpty(path)) {
throw new HandlerException(Consts.TAG + "Parameter is invalid!");
} else {
// 获取 PathReplaceService 接口的实例,该接口需要用户来实现,若没有实现则返回 null
// 若有实现则调用其 forString() 方法传入用户的 RoutePath 进行路径的预处理
PathReplaceService pService = ARouter.getInstance().navigation(PathReplaceService.class);
if (null != pService) {
path = pService.forString(path);
}
// 通过 extractGroup() 方法对字符串 path 进行截取处理,取出 Route Group 的名称部分
// 然后继续调用重载的 build() 方法,传入路径 path、Route Group 的名称部分
return build(path, extractGroup(path), true);
}
}
/**
* Build postcard by path and group
*/
protected Postcard build(String path, String group, Boolean afterReplace) {
if (TextUtils.isEmpty(path) || TextUtils.isEmpty(group)) {
throw new HandlerException(Consts.TAG + "Parameter is invalid!");
} else {
if (!afterReplace) {
PathReplaceService pService = ARouter.getInstance().navigation(PathReplaceService.class);
if (null != pService) {
path = pService.forString(path);
}
}
return new Postcard(path, group);
}
}
}
_ARouter#build() 的重载方法中,由于入参 afterReplace 为 true,因此根据入参 path 和 group 直接新建 Postcard 实例对象并返回。
2.3 _ARouter#navigation()
通过上面的分析可知,ARouter#navigation() 方法,最终也是委托给 _ARouter#navigation() 进行处理的,因此不再贴 ARouter#navigation() 方法的代码,直接看 _ARouter#navigation() 方法的实现:
final class _ARouter {
/**
* Use router navigation.
*
* @param context Activity or null.
* @param postcard Route metas
* @param requestCode RequestCode
* @param callback cb
*/
protected Object navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
PretreatmentService pretreatmentService = ARouter.getInstance().navigation(PretreatmentService.class);
if (null != pretreatmentService && !pretreatmentService.onPretreatment(context, postcard)) {
return null; // 预处理失败,取消页面跳转
}
// 如果没有传入 Context,则使用 ARouter 初始化时传入的 Application 作为 Context
postcard.setContext(null == context ? mContext : context);
try {
//通过 LogisticsCenter.completion() 方法对 postcard 进行补全
LogisticsCenter.completion(postcard);
} catch (NoRouteFoundException ex) {
logger.warning(Consts.TAG, ex.getMessage());
if (debuggable()) {
// Show friendly tips for user.
runInMainThread(new Runnable() {
@Override
public void run() {
Toast.makeText(mContext, "There's no route matched!\n" +
" Path = [" + postcard.getPath() + "]\n" +
" Group = [" + postcard.getGroup() + "]", Toast.LENGTH_LONG).show();
}
});
}
// 通过 NavigationCallback 对 navigation 的过程进行监听
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);
}
// 如果设置了 greenChannel,会跳过所有拦截器的执行
if (!postcard.isGreenChannel()) { // It must be run in async thread, maybe interceptor cost too mush time made ANR.
// 调用 InterceptorService.doInterceptions() 方法,对 postcard 的所有拦截器进行执行
interceptorService.doInterceptions(postcard, new InterceptorCallback() {
/**
* Continue process
* @param postcard route meta
*/
@Override
public void onContinue(Postcard postcard) {
_navigation(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(postcard, requestCode, callback);
}
return null;
}
}
_ARouter#navigation() 方法的执行流程如下:
- 通过 LogisticsCenter#completion() 方法对 Postcard 进行补全;
- 如果 Postcard 没有设置 greenChannel,则对 Postcard 的拦截器进行执行,执行完成后调用 _navigation 方法真正实现跳转;如果设置了 greenChannel,即绿色免检通道,则直接跳过所有拦截器,直接调用 _navigation 方法来跳转。
2.3.1 LogisticsCenter#completion()
public class LogisticsCenter {
public synchronized static void completion(Postcard postcard) {
if (null == postcard) {
throw new NoRouteFoundException(TAG + "No postcard!");
}
// 通过 Warehouse.routes.get 由 postcard.path 路径尝试获取 RouteMeta
RouteMeta routeMeta = Warehouse.routes.get(postcard.getPath());
if (null == routeMeta) { // 若 routeMeta 为 null,可能是并不存在,或是还没有加载进来
if (!Warehouse.groupsIndex.containsKey(postcard.getGroup())) {
throw new NoRouteFoundException(TAG + "There is no route match the path [" + postcard.getPath() + "], in group [" + postcard.getGroup() + "]");
} else {
// 加载路由并将其缓存到内存中,然后 routeMeta 中删除
try {
// 调用 addRouteGroupDynamic() 方法,如果 Warehouse.groupsIndex 中包含 postcard.getGroup(),但是还未加载
// 则将其移除后,重新 loadInto 进来
addRouteGroupDynamic(postcard.getGroup(), null);
if (ARouter.debuggable()) {
logger.debug(TAG, String.format(Locale.getDefault(), "The group [%s] has already been loaded, trigger by [%s]", postcard.getGroup(), postcard.getPath()));
}
} catch (Exception e) {
throw new HandlerException(TAG + "Fatal exception when loading group meta. [" + e.getMessage() + "]");
}
// 重新调用 completion() 方法对其进行补全
completion(postcard);
}
} else {
// 如果找到了对应的 routeMeta,将它的信息设置进 postcard 中
postcard.setDestination(routeMeta.getDestination());
postcard.setType(routeMeta.getType());
postcard.setPriority(routeMeta.getPriority());
postcard.setExtra(routeMeta.getExtra());
Uri rawUri = postcard.getUri();
if (null != rawUri) { // 将获取到的 uri 中的参数设置进 bundle 中
Map<String, String> resultMap = TextUtils.splitQueryParameters(rawUri);
Map<String, Integer> paramsType = routeMeta.getParamsType();
if (MapUtils.isNotEmpty(paramsType)) {
// 针对由 @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());
}
// 对 provider 和 fragment,进行特殊处理
switch (routeMeta.getType()) {
case PROVIDER: // if the route is provider, should find its instance
// 如果是一个 provider,尝试从 Warehouse 中查找它的类并构造对象,然后将其设置到 provider
Class<? extends IProvider> providerMeta = (Class<? extends IProvider>) routeMeta.getDestination();
IProvider instance = Warehouse.providers.get(providerMeta);
if (null == instance) { // 如果没有找到 provider 的实例对象
IProvider provider;
try { // 新建 provider 实例对象并初始化,然后加入到 Warehouse.providers 中
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 需要跳过所有的拦截器,因此需设置 greenChannel 为 true
break;
case FRAGMENT:
postcard.greenChannel(); // Fragment 也不需要拦截器,因此设置 greenChannel 为 true
default:
break;
}
}
}
}
LogisticsCenter#completion() 的执行流程如下:
- 通过 Warehouse.routes.get 由 path 路径尝试获取 RouteMeta 对象;
- 若获取不到 RouteMeta 对象,可能是不存在或是还没有进行加载(第一次都未加载),尝试获取 RouteGroup 调用其 loadInto 方法将 RouteMeta 加载进 Warehouse.routes,最后调用 completion 重新尝试补全;
- 若获取到 RouteMeta 对象,则将 RouteMeta 的信息设置到 postcard 中,其中会将 rawUri 的参数设置进 Bundle;
- 对于 Provider 和 Fragment 特殊处理,其中 Provider 会从 Warehouse 中加载并构造它的对象,然后设置到 postcard。注意:Provider 和 Fragment 都会跳过拦截器。
2.3.2 _ARouter#_navigation()
final class _ARouter {
private Object _navigation(final Postcard postcard, final int requestCode, final NavigationCallback callback) {
final Context currentContext = postcard.getContext();
switch (postcard.getType()) { // 根据 postcard 的 type 来分别处理
case ACTIVITY:
// 对 Activity,构造 Intent,将参数设置进去
final Intent intent = new Intent(currentContext, postcard.getDestination());
intent.putExtras(postcard.getExtras());
// 设置 flags
int flags = postcard.getFlags();
if (0 != flags) {
intent.setFlags(flags);
}
// 不是 activity,需要设置 FLAG_ACTIVITY_NEW_TASK,新建一个栈
if (!(currentContext instanceof Activity)) {
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
}
// 设置 Actions
String action = postcard.getAction();
if (!TextUtils.isEmpty(action)) {
intent.setAction(action);
}
// 切换到主线程,调用 startActivity() 方法,启动新的页面
runInMainThread(new Runnable() {
@Override
public void run() {
startActivity(requestCode, currentContext, intent, postcard, callback);
}
});
break;
case PROVIDER:
// provider 类型的直接返回对应的 provider
return postcard.getProvider();
case BOARDCAST:
case CONTENT_PROVIDER:
case FRAGMENT:
// 对于 broadcast、contentprovider、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;
}
}
_ARouter#_navigation() 函数的执行流程如下:
- 对于 Activity,会构造一个 Intent 并将之前 postcard 中的参数设置进去,之后会切换到主线程,调用 startActivity() 方法,启动新的页面;
- 对于 Provider,直接返回其对应的 provider 对象;
- 对于 Broadcast、ContentProvider、Fragment,反射构造对象后,将参数设置进去并返回。
通过分析 ARouter 的初始化和路由跳转的整体逻辑,可以发现其实整体还是不复杂的,实际上就是对 Activity 、 Fragment 的调转过程进行了包装。
3. ARouter 获取 Service
ARouter 除了可以通过 ARouter#getInstance()#build()#navigation() 这样的方式实现页面跳转之外,还可以通过ARouter#getInstance()#navigation(XXService.class) 这样的方式实现跨越组件的服务获取,来看看其是如何实现的:
public <T> T navigation(Class<? extends T> service) {
return _ARouter.getInstance().navigation(service);
}
ARouter 还是委托给 _ARouter 来实现:
final class _ARouter {
protected <T> T navigation(Class<? extends T> service) {
try {
// 通过 LogisticsCenter.buildProvider 传入 service.class 的 name 构建一个 postcard
Postcard postcard = LogisticsCenter.buildProvider(service.getName());
// Compatible 1.0.5 compiler sdk.
// 早期版本没有使用完全限定名来获取服务
if (null == postcard) {
// 没有服务,或者这个服务是旧版本,因此再次通过 LogisticsCenter.buildProvider
// 传入 simpleName 来构建 postcard
postcard = LogisticsCenter.buildProvider(service.getSimpleName());
}
if (null == postcard) {
return null; // 仍然获取不到则返回 null
}
// Set application to postcard.
postcard.setContext(mContext);
// 对 postcard 进行补全,前面分析过
LogisticsCenter.completion(postcard);
// 通过 postcard.getProvider 获取对应的 Provider 并返回
return (T) postcard.getProvider();
} catch (NoRouteFoundException ex) {
logger.warning(Consts.TAG, ex.getMessage());
return null;
}
}
}
_ARouter#navigation() 函数,首先通过 LogisticsCenter#buildProvider() 方法传入 service.class 的类名 name 构建出一个 postcard。如果获取不到,则再次通过 LogisticsCenter#buildProvider() 方法传入 simpleName 来构建出一个 postcard。随后通过 LogisticsCenter#completion() 方法对 postcard 进行补全,最后通过 postcard#getProvider() 方法获取对应的 Provider 实例对象。
总结
结合 ARouter 路由调用时序图,对 ARouter 的路由过程做个总结:
- 通过 ARouter 中的 build(path) 方法构建出一个 Postcard,或直接通过其 navigate(serviceClass) 方法构建一个 Postcard;
- 通过 Postcard 中提供的一系列方法对这次路由进行配置,包括携带的参数,是否跳过拦截器等;
- 通过 ARouter#navigation() 方法完成路由的跳转,其步骤如下:
- 通过 LogisticsCenter#completion() 方法根据 Postcard 的信息结合 Warehouse 中加载的信息对 Postcard 的 Destination、Type 等信息进行补全,这个过程中会实现对 RouteMeta 信息的装载,并且对于未跳过拦截器的类会逐个调用拦截器进行拦截器处理;
- 根据补全后 Postcard 的具体类型,调用对应的方法进行路由的过程(如:对于 Activity 调用其 startActivity() 方法,对于 Fragment 构建对象并调用其 setArgument() 方法)。
参考
github.com/alibaba/ARouter