ARouter解析六:拦截器

作者: juexingzhe | 来源:发表于2017-07-20 00:40 被阅读2724次

    今天我们接着来拆下ARouter的拦截器,这个是ARouter路由框架的第六篇分享了。在Android系统自带的startActivity的方法调用后,我们是没有办法在跳转的中间过程插入一些其它处理的,ARouter中的拦截器就实现了这种功能,可以在跳转过程中添加自定义的功能,比如添加携带参数,判断是否需要登录等,是针对AOP切面编程思想的实现。

    今天拦截器的分享会涉及到多线程,APT等技术,有一些反复的内容在前面的系列文章说过了,比如ARouter单例初始化过程,PostCard等内容,这里就不再赘述了。今天我们从下面几个方面来解析拦截器:

    1.拦截器的注册

    2.拦截器的初始化

    3.拦截器的拦截过程源码分析

    开始之前我们先看下官方Demo中拦截器的实现效果:

    拦截器.png

    点击拦截,会弹出拦截提示框点击加点料就会拦截,在拦截器中添加参数:

    拦截.png 拦截添加跳转参数.png

    好了,开始我们的拦截器之旅吧~~~

    1.拦截器注册

    拦截器使用和Activity@Route差不多,只不过拦截器是使用另外一个注解@Interceptor。有两个变量,priority是拦截器的优先级,值越小优先级越高,会优先拦截。name是拦截器的名称,开发也不怎么用。

    @Target({ElementType.TYPE})
    @Retention(RetentionPolicy.CLASS)
    public @interface Interceptor {
        /**
         * The priority of interceptor, ARouter will be excute them follow the priority.
         */
        int priority();
    
        /**
         * The name of interceptor, may be used to generate javadoc.
         */
        String name() default "Default";
    }
    

    拦截器都默认实现IInterceptor接口:

    public interface IInterceptor extends IProvider {
    
        /**
         * The operation of this interceptor.
         *
         * @param postcard meta
         * @param callback cb
         */
        void process(Postcard postcard, InterceptorCallback callback);
    }
    
    

    在拦截器实现类使用注解@Interceptor会自动注册,就是在编译期间会自动生成映射关系类,使用到的就是APT技术,不了解的小伙伴可参考Android模块开发之APT技术.
    这里因为是自动注册的,所以可以将不同功能的拦截器放在不同功能的模块中,只有模块被打包到整个项目中,因为自动注册机制所以拦截器就会生效,如果不将这些拦截器放到模块并打包到项目中,那就不会生效,这样就不用去做很多注册与反注册的工作,这也是ARouter适用于模块开发的原因之一。
    看看自动生成的映射关系类是怎样,官方Demo提供了两个拦截器,Test1Interceptor拦截器在app模块中,TestInterceptor90拦截器在test-module-1中。下面文件路径ARouter/app/build/generated/source/apt/debug/com/alibaba/android/arouter/routes/ARouter$$Interceptors$$app.java

    public class ARouter$$Interceptors$$app implements IInterceptorGroup {
      @Override
      public void loadInto(Map<Integer, Class<? extends IInterceptor>> interceptors) {
        interceptors.put(7, Test1Interceptor.class);
      }
    }
    

    下面文件路径:ARouter/test-module-1/build/generated/source/apt/release/com/alibaba/android/arouter/routes/ARouter$$Interceptors$$testmodule1.java

    public class ARouter$$Interceptors$$testmodule1 implements IInterceptorGroup {
      @Override
      public void loadInto(Map<Integer, Class<? extends IInterceptor>> interceptors) {
        interceptors.put(90, TestInterceptor90.class);
      }
    }
    

    生成的映射关系类就实现一个功能,将拦截器实现类加载缓存。接下来我们看看拦截器在什么时候加载进缓存,也就是初始化。

    2.拦截器初始化

    因为拦截器是在每次跳转中间都需要判断的,所以在ARouter初始化的时候就会加载进来,初始化过程肯定需要和编译期间生成的映射关系类打交道,所以逻辑自然就放在LogisticsCenter中。我们看看初始化的源码,和拦截器无关的逻辑我做了删减,为了看起来方便点。

    public synchronized static void init(Context context, ThreadPoolExecutor tpe) throws HandlerException {
    
            try {
                // These class was generate by arouter-compiler.
                List<String> classFileNames = ClassUtils.getFileNameByPackageName(mContext, ROUTE_ROOT_PAKCAGE);
    
                for (String className : classFileNames) {
                    if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_INTERCEPTORS)) {
                        // Load interceptorMeta
                        ((IInterceptorGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.interceptorsIndex);
                    } 
                }
    
                if (Warehouse.groupsIndex.size() == 0) {
                    logger.error(TAG, "No mapping files were found, check your configuration please!");
                }
    
                if (ARouter.debuggable()) {
                    logger.debug(TAG, String.format(Locale.getDefault(), "LogisticsCenter has already been loaded, GroupIndex[%d], InterceptorIndex[%d], ProviderIndex[%d]", Warehouse.groupsIndex.size(), Warehouse.interceptorsIndex.size(), Warehouse.providersIndex.size()));
                }
            } catch (Exception e) {
                throw new HandlerException(TAG + "ARouter init logistics center exception! [" + e.getMessage() + "]");
            }
    }
    

    可以看出来,拦截器映射关系会加载到仓库的mapWarehouse.interceptorsIndex 中,要分清楚这个时候只是代码缓存中知道了有框架中有哪些拦截器,但是还没初始化
    我在这里下了个断点,看看有几个拦截器。
    可以看出来有两个映射文件,这个和前面注册的分析是一样的。

    拦截器个数.png

    再看看加载到仓库中的是不是两个,可以看出来确实是两个,好厉害:)

    仓库拦截器.png

    到这里只是找到了拦截器,还没有进行初始化,我们接着看源码,在_ARouter.init就是主要工作就是调用LogisticsCenter进行初始化,这里就是上面的分析工程,然后会调用_ARouter.afterInit()

    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.afterInit(),获取interceptorService

    static void afterInit() {
            // Trigger interceptor init, use byName.
            interceptorService = (InterceptorService) ARouter.getInstance().build("/arouter/service/interceptor").navigation();
        }
    

    在获取interceptorService过程中会在LogisticsCenter中实例化interceptorService实现类InterceptorServiceImpl后调用init(Context)方法。

    switch (routeMeta.getType()) {
                    case PROVIDER:  
                        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) {
                                throw new HandlerException("Init provider failed! " + e.getMessage());
                            }
                        }
                        postcard.setProvider(instance);
                        postcard.greenChannel();    // Provider should skip all of interceptors
                        break;
                    case FRAGMENT:
                        postcard.greenChannel();    // Fragment needn't interceptors
                    default:
                        break;
    }
    

    我们接着跟到init方法中,可以看出,就是从仓库Warehouse.interceptorsIndex中取出拦截器然后进行实例化,接着将实例对象添加到Warehouse.interceptors 中。

    @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();
                        }
                    }
                }
            });
    }
    

    到这里拦截器的初始化过程就比较清楚了,有一点需要知道,就是拦截器怎么根据优先级进行拦截?原理就在仓库的两个缓存map中,一个就是存储拦截器类的Warehouse.interceptorsIndex,另外一个就是存储拦截器实例的Warehouse.interceptors.我们看下这两个类型:

    // Cache interceptor
    static Map<Integer, Class<? extends IInterceptor>> interceptorsIndex = new UniqueKeyTreeMap<>("More than one interceptors use same priority [%s]");
    static List<IInterceptor> interceptors = new ArrayList<>();
    

    其中interceptorsIndex的类型是TreeMap,原理是红黑树,可以实现按顺序存储,所以我们拦截器就可以按照优先级存储在这个缓存中。实例化的时候也是按照优先级取出,然后实例化存储到ArrayList中,优先级值越小就在队列中越靠前,这就是拦截器优先级的原理。细节处见功夫啊!

    接下来就看看拦截器是怎么拦截的~~~

    3.拦截器的拦截过程源码分析

    拦截是在跳转之前拦截,所以我们到_ARouter中的navigation去看,如果需要拦截就通过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);
    }
    

    interceptorServiceInterceptorServiceImpl的实例对象.

    @Route(path = "/arouter/service/interceptor")
    public class InterceptorServiceImpl implements InterceptorService
    

    我们接着看拦截方法:

    1.首先看下Warehouse.interceptors是否有拦截器,如果没有就直接调用回调接口跳转;有拦截器就进行后面步骤。

    public void doInterceptions(final Postcard postcard, final InterceptorCallback callback) {
            //1.是否有拦截器
            if (null != Warehouse.interceptors && Warehouse.interceptors.size() > 0) {
    
                //2.检查拦截器初始化情况
                checkInterceptorsInitStatus();
    
                if (!interceptorHasInit) {//未初始化就抛异常
                    callback.onInterrupt(new HandlerException("Interceptors initialization takes too much time."));
                    return;
                }
    
                //3.拦截
                LogisticsCenter.executor.execute(new Runnable() {
                    @Override
                    public void run() {
                        CancelableCountDownLatch interceptorCounter = new CancelableCountDownLatch(Warehouse.interceptors.size());
                        try {
                            _excute(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(new HandlerException(postcard.getTag().toString()));
                            } else {
                                callback.onContinue(postcard);
                            }
                        } catch (Exception e) {
                            callback.onInterrupt(e);
                        }
                    }
                });
            } else {
                callback.onContinue(postcard);
            }
        }
    

    2.首先检查拦截器的初始化情况checkInterceptorsInitStatus,这是个同步方法,拦截器未初始化就会等待一定时间,超时就抛出错误。在前面init 方法是在线程池中异步执行的,中如果初始化成功就会释放锁interceptorInitLock。为什么要异步?因为拦截器可能数目比较多或者初始化比较耗时。

    synchronized (interceptorInitLock) {
            interceptorInitLock.notifyAll();
    }
    

    接着看看checkInterceptorsInitStatus方法,当拦截器还没初始化完成,这里就在锁上同步等待时间10 * 1000,超时还未获取到锁就会报错。

    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() + "]");
                    }
                }
            }
        }
    

    拦截器都初始化成功后,来到第三步拦截实现,拦截也是在线程池中异步执行,同上面init的原因一样。

    3.拦截过程就在_excute方法中实现,这个我们在第四步中再分析。拦截器不可能无限时长拦截吧,那么怎么实现?就是通过Java提供的异步工具CountDownLatch.在等待interceptorCounter.await(postcard.getTimeout(), TimeUnit.SECONDS);后进行判断interceptorCounter.getCount是否等于0或者postcard.getTag是否是空,如果满足就是拦截出现问题;否则就继续放行。

    接着看看拦截的真正实现逻辑:

    3.拦截真正过程在_execute中, 逻辑比较简单,就是依次取出拦截器实例,然后调用process方法,传入回调接口InterceptorCallback,当前拦截器处理完毕回调onContinue,在这里面递减counter.countDown,然后取出下一个拦截器循环上面过程。

    private static void _excute(final int index, final CancelableCountDownLatch counter, final Postcard postcard) {
            if (index < Warehouse.interceptors.size()) {
                IInterceptor iInterceptor = Warehouse.interceptors.get(index);
                iInterceptor.process(postcard, new InterceptorCallback() {
                    @Override
                    public void onContinue(Postcard postcard) {
                        // Last interceptor excute over with no exception.
                        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.
                        postcard.setTag(null == exception ? new HandlerException("No message.") : exception.getMessage());    // save the exception message for backup.
                        counter.cancel();
                    }
                });
            }
    }
    

    拦截器的实现流程就是以上。接着我们挑一个拦截器Test1Interceptor的实现看看。这个就是上面Demo演示的弹框拦截器,如果选择选择“加点料”,就会添加参数,然后调用回调接口的callback.onContinue,继续到下一个拦截器。

    @Override
    public void process(final Postcard postcard, final InterceptorCallback callback) {
            if ("/test/activity4".equals(postcard.getPath())) {
                final AlertDialog.Builder ab = new AlertDialog.Builder(MainActivity.getThis());
                ab.setCancelable(false);
                ab.setTitle("温馨提醒");
                ab.setMessage("想要跳转到Test4Activity么?(触发了\"/inter/test1\"拦截器,拦截了本次跳转)");
                ab.setNegativeButton("继续", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        callback.onContinue(postcard);
                    }
                });
                ab.setNeutralButton("算了", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        callback.onInterrupt(null);
                    }
                });
                ab.setPositiveButton("加点料", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        postcard.withString("extra", "我是在拦截器中附加的参数");
                        callback.onContinue(postcard);
                    }
                });
    
                MainLooper.runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        ab.create().show();
                    }
                });
            } else {
                callback.onContinue(postcard);
            }
    }
    
    

    4.总结

    今天的拦截器内容会比较多一点,拦截器的注册和其它的activity或者service是差不多的,主要是注解不同。拦截器的初始化过程是在线程池中进行,为了是拦截器可能耗时的问题。拦截器的拦截过程其实就是在线程池中从仓库里依次取出拦截器实例进行拦截,稍微难一点的地方就是在多线程的同步问题上,用的还是Java的多线程技术。

    有需要的小伙伴可以看下前面的系列分享:
    ARouter解析一:基本使用及页面注册源码解析
    ARouter解析二:页面跳转源码分析
    ARouter解析三:URL跳转本地页面源码分析
    ARouter解析四:发现服务和Fragment
    ARouter解析五:IoC与依赖注入

    今天的拦截器的内容就是这样,希望对大家有点帮助,谢谢!

    欢迎关注公众号:JueCode

    相关文章

      网友评论

      • liaowenhao:“接着看看checkInterceptorsInitStatus方法,当拦截器还没初始化完成,这里就在锁上同步等待时间10 * 1000,超时还未获取到锁就会报错。”不是这样的,如果等待时间仍然没初始化,则继续等待十秒,直到初始化成功。
        bitman:```java
        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() + "]");
        }
        }
        }
        ```
        它这里wait超时就抛出异常中断了,不会再继续等待的

      本文标题:ARouter解析六:拦截器

      本文链接:https://www.haomeiwen.com/subject/dfyrkxtx.html