阿里ARouter拦截器使用及源码解析(二)

阿里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初始化的时候,提到过_ARouterafterInit()方法,这个方法的作用就是生成一个拦截器的服务对象,然后将所有的拦截器都初始化, 保存在仓库Warehouse.interceptors中。所以拦截器的init()方法在初始化的时候就调用了。

而路由跳转的执行顺序为,先执行回调函数的onFound(),之后是拦截器2的process(),拦截器1的process(),最后执行回调函数的onArrival()注意:拦截器是按照优先级的高低进行顺序执行的,优先级也高,越早执行。

上面就是关于拦截器的使用,你可以在process()中通过postcard的属性值进行判断,然后进行拦截处理,处理成功调用callback.onContinue()方法继续往下执行,失败则调用callback.onInterrupt()方法中断跳转。其中,postcard包含了路由节点的各种信息。

拦截器的使用注意事项

接下来介绍下关于拦截器使用的注意点。

1.定义多个拦截器的时候,priority的值不能定义一样的,只要其中两个拦截器的优先值一样,编译时会报错。如下:

《阿里ARouter拦截器使用及源码解析(二)》 相同优先级的拦截器
《阿里ARouter拦截器使用及源码解析(二)》

2.在拦截器的process()方法中,如果对传入的 postcard对象设置了tag值,那么跳转会被当做拦截处理,如下图所示。通常来说,postcard的tag值会用来保存拦截处理过程中产生的异常对象。

《阿里ARouter拦截器使用及源码解析(二)》
《阿里ARouter拦截器使用及源码解析(二)》

3.在拦截器的process()方法中,如果你即没有调用callback.onContinue(postcard)方法也没有调用callback.onInterrupt(exception)方法,那么不再执行后续的拦截器,需等待300s(默认值,可设置改变)的时间,才能抛出拦截器中断。如下图所示(超时等待时间10000ms):

《阿里ARouter拦截器使用及源码解析(二)》
《阿里ARouter拦截器使用及源码解析(二)》

4.拦截器的process()方法以及带跳转的回调中的onInterrupt(Postcard postcard)方法,均是在分线程中执行的,如果需要做一些页面的操作显示,必须在主线程中执行。如下图所示:

《阿里ARouter拦截器使用及源码解析(二)》
《阿里ARouter拦截器使用及源码解析(二)》
《阿里ARouter拦截器使用及源码解析(二)》

拦截器源码分析

上面是在自定义拦截器的时候需要注意的地方,下面将从编译期一步一步的分析拦截器的原理,而关于上述注意点的原因在该部分会有解释。

  • 编译过程

关于拦截器编译处理器的全部源码可以参见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拦截器使用及源码解析(二)》

  • 拦截器初始化

在上篇文章中,我们分析到ARouter的初始化最后会调用LogisticsCenter.init(),其实这个方法的主要功能就是得到com.alibaba.android.arouter.routes包中所有的文件,然后Group 、Interceptor 、Provider 三种清单加载到 Warehouse 内存仓库中。源码在这里就不贴出来了,该方法的具体分析在阿里ARouter使用及源码解析(一)有所讲述。

《阿里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()进行初始化,如下图红框所示:

《阿里ARouter拦截器使用及源码解析(二)》

现在我们来分析下InterceptorServiceImplinit()方法。

    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;
    }

接下来,我们继续分析对路由进行拦截处理最主要的一块interceptorServicedoInterceptions()方法。

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传参自动装载的使用以及原理,后续会有文章继续分析,请各位持续关注!

    原文作者:time_fly
    原文地址: https://www.jianshu.com/p/c8d7b1379c1b
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞