阿里ARouter的分析计划
关于ARouter基本跳转的用法以及源码解析在上篇文章阿里阿里ARouter使用及源码解析(一)已经有过分析,有不清楚的同学可以去看看。本篇文章主要是关于ARouter进阶用法拦截器的使用和分析。
先自定义两个拦截器,看看跳转过程中,拦截器的执行顺序。
自定义拦截器需要实现IInterceptor
接口,并且添加@Interceptor
的注解,其中priority
为拦截器的优先级,值越小,优先级越高;然后实现pocess()
和init()
方法。
拦截器1:Test1Interceptor
@Interceptor(priority = 5)
public class Test1Interceptor implements IInterceptor {
@Override
public void process(Postcard postcard, InterceptorCallback callback) {
Log.e("testService", Test1Interceptor.class.getName() + " has process.");
//拦截跳转,进行一些处理
if (postcard.getPath().equals("/test/test1")) {
Log.e("testService", Test1Interceptor.class.getName() + " 进行了拦截处理!");
}
callback.onContinue(postcard);
}
@Override
public void init(Context context) {
Log.e("testService", Test1Interceptor.class.getName() + " has init.");
}
}
拦截器2:Test2Interceptor
@Interceptor(priority = 4)
public class Test2Interceptor implements IInterceptor {
@Override
public void process(Postcard postcard, InterceptorCallback callback) {
Log.e("testService", Test2Interceptor.class.getName() + " has process.");
//拦截跳转,进行一些处理
if (postcard.getPath().equals("/test/test1")) {
Log.e("testService", Test2Interceptor.class.getName() + " 进行了拦截处理!");
}
callback.onContinue(postcard);
}
@Override
public void init(Context context) {
Log.e("testService", Test2Interceptor.class.getName() + " has init.");
}
}
带回调函数的跳转:
ARouter.getInstance().build("/test/test1").navigation(this, new NavCallback() {
@Override
public void onFound(Postcard postcard) {
Log.e("testService", "找到了");
}
@Override
public void onLost(Postcard postcard) {
Log.e("testService", "找不到了");
}
@Override
public void onArrival(Postcard postcard) {
Log.e("testService", "跳转完了");
}
@Override
public void onInterrupt(Postcard postcard) {
Log.e("testService", "被拦截了");
}
});
跳转之后,打印的执行顺序:
执行顺序
在上篇文章中,在分析到ARouter初始化的时候,提到过_ARouter
的afterInit()
方法,这个方法的作用就是生成一个拦截器的服务对象,然后将所有的拦截器都初始化, 保存在仓库Warehouse.interceptors
中。所以拦截器的init()
方法在初始化的时候就调用了。
而路由跳转的执行顺序为,先执行回调函数的onFound()
,之后是拦截器2的process()
,拦截器1的process()
,最后执行回调函数的onArrival()
。注意:拦截器是按照优先级的高低进行顺序执行的,优先级也高,越早执行。
上面就是关于拦截器的使用,你可以在process()
中通过postcard
的属性值进行判断,然后进行拦截处理,处理成功调用callback.onContinue()
方法继续往下执行,失败则调用callback.onInterrupt()
方法中断跳转。其中,postcard
包含了路由节点的各种信息。
接下来介绍下关于拦截器使用的注意点。
1.定义多个拦截器的时候,**priority
**的值不能定义一样的,只要其中两个拦截器的优先值一样,编译时会报错。如下:
相同优先级的拦截器
2.在拦截器的**process()
**方法中,如果对传入的 postcard
**对象设置了tag值,那么跳转会被当做拦截处理,如下图所示。通常来说,**postcard
**的tag值会用来保存拦截处理过程中产生的异常对象。**
3.在拦截器的**process()
**方法中,如果你即没有调用**callback.onContinue(postcard)
**方法也没有调用**callback.onInterrupt(exception)
**方法,那么不再执行后续的拦截器,需等待300s(默认值,可设置改变)的时间,才能抛出拦截器中断。如下图所示(超时等待时间10000ms):
4.拦截器的**process()
**方法以及带跳转的回调中的**onInterrupt(Postcard postcard)
**方法,均是在分线程中执行的,如果需要做一些页面的操作显示,必须在主线程中执行。如下图所示:
上面是在自定义拦截器的时候需要注意的地方,下面将从编译期一步一步的分析拦截器的原理,而关于上述注意点的原因在该部分会有解释。
关于拦截器编译处理器的全部源码可以参见InterceptorProcessor,下面我们着重介绍它的process()
方法。
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
if (CollectionUtils.isNotEmpty(annotations)) {
//获取所有被@Interceptor注解的元素集合
Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(Interceptor.class);
try {
//具体处理注解,生成java文件的方法
parseInterceptors(elements);
} catch (Exception e) {
logger.error(e);
}
return true;
}
return false;
}
下面分析下具体的处理方法parseInterceptors()
private void parseInterceptors(Set<? extends Element> elements) throws IOException {
if (CollectionUtils.isNotEmpty(elements)) {
logger.info(">>> Found interceptors, size is " + elements.size() + " <<<");
for (Element element : elements) {
//检查被@Interceptor注解的元素是否实现了IInterceptor接口
if (verify(element)) { // Check the interceptor meta
logger.info("A interceptor verify over, its " + element.asType());
Interceptor interceptor = element.getAnnotation(Interceptor.class);
//interceptors是一个TreeMap<Integer, Element>集合,按照拦截器的优先级值作为key值进行保存拦截器元素
//通过优先级的值获取拦截器元素,如果已经存在同优先级的拦截器则抛出异常
//这个地方也解释了为什么拦截器的优先级的值不能一样
Element lastInterceptor = interceptors.get(interceptor.priority());
if (null != lastInterceptor) { // Added, throw exceptions
throw new IllegalArgumentException(
String.format(Locale.getDefault(), "More than one interceptors use same priority [%d], They are [%s] and [%s].",
interceptor.priority(),
lastInterceptor.getSimpleName(),
element.getSimpleName())
);
}
//保存拦截器元素
interceptors.put(interceptor.priority(), element);
} else {
logger.error("A interceptor verify failed, its " + element.asType());
}
}
// public static final String IROUTE_GROUP "com.alibaba.android.arouter.facade.template.IInterceptor";
//得到接口IInterceptor元素
TypeElement type_ITollgate = elementUtil.getTypeElement(IINTERCEPTOR);
// public static final String IROUTE_GROUP "com.alibaba.android.arouter.facade.template.IInterceptorGroup";
//得到接口IInterceptorGroup元素
TypeElement type_ITollgateGroup = elementUtil.getTypeElement(IINTERCEPTOR_GROUP);
/**
* 创建参数类型
*
* 创建ARouter$$Interceptors$$xxx类中方法参数Map<Integer, Class<? extends IInterceptor>>类型的名称
*/
ParameterizedTypeName inputMapTypeOfTollgate = ParameterizedTypeName.get(
ClassName.get(Map.class),
ClassName.get(Integer.class),
ParameterizedTypeName.get(
ClassName.get(Class.class),
WildcardTypeName.subtypeOf(ClassName.get(type_ITollgate))
)
);
// 创建输入的参数Map<Integer, Class<? extends IInterceptor>>名, interceptors
ParameterSpec tollgateParamSpec = ParameterSpec.builder(inputMapTypeOfTollgate, "interceptors").build();
// 创建ARouter$$Interceptors$$xxx类的loadInto方法
MethodSpec.Builder loadIntoMethodOfTollgateBuilder = MethodSpec.methodBuilder(METHOD_LOAD_INTO)
.addAnnotation(Override.class)
.addModifiers(PUBLIC)
.addParameter(tollgateParamSpec);
// 遍历保存的拦截器的集合,添加方法体
if (null != interceptors && interceptors.size() > 0) {
// Build method body
for (Map.Entry<Integer, Element> entry : interceptors.entrySet()) {
//循环添加这样的方法体: interceptors.put(priority, xxx.class);
loadIntoMethodOfTollgateBuilder.addStatement("interceptors.put(" + entry.getKey() + ", $T.class)", ClassName.get((TypeElement) entry.getValue()));
}
}
//生成java文件ARouter$$Interceptors$$xxx,xxx为moduleName
JavaFile.builder(PACKAGE_OF_GENERATE_FILE,
TypeSpec.classBuilder(NAME_OF_INTERCEPTOR + SEPARATOR + moduleName)
.addModifiers(PUBLIC)
.addJavadoc(WARNING_TIPS)
.addMethod(loadIntoMethodOfTollgateBuilder.build())
.addSuperinterface(ClassName.get(type_ITollgateGroup))
.build()
).build().writeTo(mFiler);
logger.info(">>> Interceptor group write over. <<<");
}
}
verify(Element element)
目的是检查被@Interceptor注解的元素是否实现了IInterceptor接口
private boolean verify(Element element) {
Interceptor interceptor = element.getAnnotation(Interceptor.class);
return null != interceptor && ((TypeElement) element).getInterfaces().contains(iInterceptor);
}
其实拦截器编译处理器原理很简单,就是生成一个ARouter$$Interceptors$$xxx.java文件,如下图所示:
在上篇文章中,我们分析到ARouter的初始化最后会调用LogisticsCenter.init()
,其实这个方法的主要功能就是得到com.alibaba.android.arouter.routes
包中所有的文件,然后Group 、Interceptor 、Provider 三种清单加载到 Warehouse 内存仓库中。源码在这里就不贴出来了,该方法的具体分析在阿里ARouter使用及源码解析(一)有所讲述。
从上图可以看到,通过反射实例化ARouter$$Interceptors$$xxx类,并且调用它的loadInto()
方法,将所有的拦截器清单加载到 Warehouse.interceptorsIndex
内存仓库中去。
将拦截器清单加载到内存后,会调用_ARouter.afterInit()
,此方法是获取拦截器的服务对象InterceptorServiceImpl
。
static void afterInit() {
// Trigger interceptor init, use byName.
interceptorService = (InterceptorService) ARouter.getInstance().build("/arouter/service/interceptor").navigation();
}
关于获取服务对象的原理,在上篇文章中有分析,在这里就不作过多的讲解了。在获取服务对象过程中,会调用到LogisticsCenter.completion()
方法,其中会实例化InterceptorServiceImpl
类,并调用init()
进行初始化,如下图红框所示:
现在我们来分析下InterceptorServiceImpl
的init()
方法。
public void init(final Context context) {
//executor是从_ARouter中传入的线程池,新建分线程进行初始化
LogisticsCenter.executor.execute(new Runnable() {
@Override
public void run() {
//遍历拦截器清单,实例化后调用init为每个拦截器初始化,最后添加到内存仓库中去
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.");
// 获取同步锁后,唤醒checkInterceptorsInitStatus()中的线程等待,checkInterceptorsInitStatus()下面会有分析
synchronized (interceptorInitLock) {
interceptorInitLock.notifyAll();
}
}
}
});
}
init()
方法主要作用就是遍历拦截器清单,然后通过反射实例化拦截器,并且将其保存在内存仓库Warehouse.interceptors
中,跟路由按组加载,第一次跳转的时候加载相应的组不同,拦截器在初始化的时候就全部加载到内存中去了,因为拦截器会在任意一次跳转中生效,而且数量不会太多。
在跳转的navigation()
方法中,会对未设置greenChannel属性所有进行拦截处理。
protected Object navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
//代码省略
......
//如果没有将greenChannel属性设为true,都要调用interceptorService服务,进行拦截处理
if (!postcard.isGreenChannel()) { // It must be run in async thread, maybe interceptor cost too mush time made ANR.
interceptorService.doInterceptions(postcard, new InterceptorCallback() {
/**
* Continue process
*
* @param postcard route meta
*/
@Override
public void onContinue(Postcard postcard) {
_navigation(context, 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(context, postcard, requestCode, callback);
}
return null;
}
接下来,我们继续分析对路由进行拦截处理最主要的一块interceptorService
的doInterceptions()
方法。
public void doInterceptions(final Postcard postcard, final InterceptorCallback callback) {
if (null != Warehouse.interceptors && Warehouse.interceptors.size() > 0) {
//当拦截器数不为0时,执行下面的代码。
//首先检查拦截器初始化的状态
checkInterceptorsInitStatus();
//如果跳转的时候还未初始化完成,则中断跳转
if (!interceptorHasInit) {
callback.onInterrupt(new HandlerException("Interceptors initialization takes too much time."));
return;
}
// 新建分线程,执行拦截处理的操作。
LogisticsCenter.executor.execute(new Runnable() {
@Override
public void run() {
//创建一个CountDownLatch同步工具类,关于其功能下面会有简单介绍
CancelableCountDownLatch interceptorCounter = new CancelableCountDownLatch(Warehouse.interceptors.size());
try {
//执行每一个拦截器
_excute(0, interceptorCounter, postcard);
//阻塞线程,当超过设置的超时,再执行下面的代码;
//当设置的计数值Warehouse.interceptors.size()减为0时,也会唤醒执行下面的代码
interceptorCounter.await(postcard.getTimeout(), TimeUnit.SECONDS);
//当执行完所有拦截器的process()方法后,检查是否通过拦截
if (interceptorCounter.getCount() > 0) {
callback.onInterrupt(new HandlerException("The interceptor processing timed out."));
} else if (null != postcard.getTag()) { // Maybe some exception in the tag.
callback.onInterrupt(new HandlerException(postcard.getTag().toString()));
} else {
callback.onContinue(postcard);
}
} catch (Exception e) {
callback.onInterrupt(e);
}
}
});
} else {
callback.onContinue(postcard);
}
}
我们再看看检查拦截器初始化的状态的源码。
private static void checkInterceptorsInitStatus() {
synchronized (interceptorInitLock) {
while (!interceptorHasInit) {
try {
interceptorInitLock.wait(10 * 1000);
} catch (InterruptedException e) {
throw new HandlerException(TAG + "Interceptor init cost too much time error! reason = [" + e.getMessage() + "]");
}
}
}
}
检查拦截器初始化状态很简单,获取到同步锁之后,不断的检查interceptorHasInit
值是否为true,如果还没初始化完成,就阻塞线程释放同步锁,等待10s后继续争夺同步锁检查是否初始化完成。在这里需要注意的是,这段代码是在主线程中执行的,所以这意味着阻塞了主线程。如果我们在拦截器的初始化的时候进行了耗时的操作,在没有初始化完成就开始点击跳转,这个时候主线程就被阻塞在这了,很容易造成ANR
最后我们再来下最后一个_excute()
方法。
private static void _excute(final int index, final CancelableCountDownLatch counter, final Postcard postcard) {
if (index < Warehouse.interceptors.size()) {
//通过递归的方法不断调用每个拦截器的process()方法
IInterceptor iInterceptor = Warehouse.interceptors.get(index);
iInterceptor.process(postcard, new InterceptorCallback() {
@Override
public void onContinue(Postcard postcard) {
// 通过拦截器,同步工具的数值减一.
counter.countDown();
//执行下一个拦截器
_excute(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 excute over with fatal exception.
// 未通过拦截器,设置异常tag标志,在上面doInterceptions()中判断null != postcard.getTag(),执行拦截的回调
postcard.setTag(null == exception ? new HandlerException("No message.") : exception.getMessage()); // save the exception message for backup.
counter.cancel();
}
});
}
}
关于拦截器的执行过程的原理,基本已简单分析完毕。下面简单介绍下CancelableCountDownLatch 同步工具,其实CancelableCountDownLatch 是自定义的CountDownLatch的实现类。这个类,就增加了一个cancel()
方法,当未通过拦截的时候,调用此方法,循环减少同步工具的计数值。
public class CancelableCountDownLatch extends CountDownLatch {
public CancelableCountDownLatch(int count) {
super(count);
}
public void cancel() {
while (getCount() > 0) {
countDown();
}
}
}
CountDownLatch是一个同步工具类,它允许一个或多个线程一直等待,直到其他线程的操作执行完后再执行。CountDownLatch在创建的时候传入一个计数值,这个值为你执行任务的数量,然后再调用wait()方法,调用wait()方法的线程就阻塞在这,不再往下执行。当每完成一个任务调用countDown()方法,计数值减一。当计数器值到达0时,它表示执行完了所有任务,最后阻塞的线程就可以恢复执行下面的代码了。
当执行完所有的拦截器的process()
方法后,执行interceptorCounter.await(postcard.getTimeout(), TimeUnit.SECONDS)
代码,当等待超过设置的超时时间,或者计数值减为0后,继续往下执行。这个时候,根据相应判断,是拦截成功还是通过拦截。
interceptorCounter.getCount() > 0
,同步工具的计数值是否大于0,如果大于0说明拦截器没有全部执行完成,这个时候回调onInterrupt()
方法,表示未通过拦截器。出现这种情况的原因是在某个拦截器的process()
方法中,既没调用callback的onContinue()
也没调用onInterrupt()
方法,导致没有调用同步工具的countDown()
方法,以至于_excute()
执行完毕,然后执行interceptorCounter.await(postcard.getTimeout(), TimeUnit.SECONDS)
后,同步工具的计数值未减为0。这里也解释了上面拦截器使用注意事项的第三点。null != postcard.getTag()
,如果在process()
方法中,没有通过拦截器调用callback.onInterrupt()
后,会设置tag;或者我们在process()
方法中认为的设置tag值,这样也不会通过拦截器。这里也解释了上面拦截器使用注意事项的第二点。onInterrupt()
的回调也是在分线程中。这里也解释了上面拦截器使用注意事项的第四点,路由跳转传入的回调函数中的**onInterrupt()
**方法是在分线程中执行的。onContinue()
方法,在此方法中调用_ARouter
中的_navigation()
方法,进行最后的跳转操作。该方法在上篇文章中已有讲述。关于ARouter拦截器的使用以及源码解析到这里就分析完毕了,如果各位同学觉得本篇文章对你有所帮助的话,请点个喜欢,谢谢!若有分析不对之处,也希望各位同学能够指出错误。
关于最后ARouter传参自动装载的使用以及原理,后续会有文章继续分析,请各位持续关注!