组件化基础ARouter(四、拦截器)

一、ARouter概述

  ARouter是一个用于帮助Android App进行组件化改造的框架 —— 支持模块间的路由、通信、解耦。ARouter的典型应用场景有:

  1. 从外部URL映射到内部页面,以及参数传递与解析;
  2. 跨模块页面跳转,模块间解耦;
  3. 拦截跳转过程,处理登陆、埋点等逻辑;
  4. 跨模块API调用,通过控制反转来做组件解耦;
      本篇主要介绍ARouter的用法之一:拦截跳转。拦截跳转比较经典的应用就是在跳转过程中处理登陆事件,这样就不需要在目标页重复做登陆检查。

二、基础用法

  通过一个简单的例子来介绍拦截跳转的应用场景:在MainActivity中有两个文本框分别用来输入name和age,点击Button后打开模块first_library模块中的FirstActivity:

  ARouter跳转Activity的方式是:ARouter.getInstance().build(“/first/activity”).navigation();(详细分析见:“组件化基础ARouter(一)”),本篇的拦截器就是希望在执行navigation操作时进行拦截:(1)判断name和age是否为空,为空则拦截,不为空则继续跳转;(2)记录启动日志。ARouter拦截器的基础用法如下:

2.1 IInterceptor

  ARouter提供了IInterceptor接口和@Interceptor注解,供开发者实现自定义拦截器。IInterceptor拦截的方法在process()中,在方法中调用callback.onInterrupt()则拦截跳转,调用callback.onContinue()则继续跳转。

2.2 @Interceptor

  ARouter的@Interceptor注解中可以设置Inteceptor的优先级。判断name和age是否为空的Interceptor如下:

@Interceptor(priority = 1, name = "登陆检查拦截器")
public class LoginInterceptor implements IInterceptor {

    @Override
    public void process(Postcard postcard, InterceptorCallback callback) {
        if (postcard.getPath().equals("/first/activity")) {
            String name = postcard.getExtras().getString("name");
            int age = postcard.getExtras().getInt("age");
            if (TextUtils.isEmpty(name) || age < 0) {
                callback.onInterrupt(new RuntimeException());
            }
        }
        callback.onContinue(postcard);
    }

    @Override
    public void init(Context context) { }
}

  记录启动日志的Interceptor如下:

@Interceptor(priority = 2, name = "打点拦截器")
public class NavigationLogInterceptor implements IInterceptor {

    @Override
    public void process(Postcard postcard, InterceptorCallback callback) {
        Log.d("NavigationDebug", "upload navigation log");
        callback.onContinue(postcard);
    }

    @Override
    public void init(Context context) { }
}

2.3 NavigationCallback

  在实现了拦截器后,在ARouter执行跳转时传入NavigationCallback参数,就可以收到拦截器是否拦截的回调:

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    private NavigationCallback navigationCallback = new NavigationCallback() {
        @Override
        public void onFound(Postcard postcard) { }
        @Override
        public void onLost(Postcard postcard) { }
        @Override
        public void onArrival(Postcard postcard) { }
        @Override
        public void onInterrupt(Postcard postcard) {
            // 拦截器拦截后,会收到onInterrupt回调
            Toast.makeText(MainActivity.this, "navigation interrupt", Toast.LENGTH_SHORT).show();
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (BuildConfig.DEBUG) {
            ARouter.openDebug();
            ARouter.openLog();
        }
        ARouter.init(getApplication());
    }

    @Override
    public void onClick(View v) {
        if (v.getId() == R.id.main_button) {
            String name = nameTextView.getText().toString();
            int age = Integer.parseInt(ageTextView.getText().toString());
            // 跳转时传入navigationCallback参数
            ARouter.getInstance().build("/first/activity")
                    .withInt("age", age)
                    .withString("name", name)
                    .navigation(null, navigationCallback);
        }
    }
}

  至此,拦截器的基础用法就介绍完毕,打开Demo可以看到拦截器已经正常工作了。

三、源码分析

  ARouter的跳转原理在"组件化基础ARouter(一)"中已经进行过分析,这里不再介绍,下面在跳转的基础上分析拦截器的实现源码。

3.1 注解处理@Interceptor

  与"组件化基础ARouter(一)"类似,对于@Interceptor注解,ARouter会自动在如下位置生成代码:

  自动生成的代码如下:

/**
 * DO NOT EDIT THIS FILE!!! IT WAS GENERATED BY AROUTER. */
public class ARouter$$Interceptors$$lib_interceptor implements IInterceptorGroup {
  @Override
  public void loadInto(Map<Integer, Class<? extends IInterceptor>> interceptors) {
    interceptors.put(1, LoginInterceptor.class);
    interceptors.put(2, NavigationLogInterceptor.class);
  }
}

3.2 初始化SDK

  在"组件化基础ARouter(一)"中分析过ARouter的初始化过程。对于自定义拦截器的处理不同之处在于,对自定义拦截器以UniqueKeyTreeMap的形式存放在Warehouse.interceptorsIndex中。

public class LogisticsCenter {
    private static Context mContext;
    static ThreadPoolExecutor executor;
    private static boolean registerByPlugin;

    public synchronized static void init(Context context, ThreadPoolExecutor tpe) throws HandlerException {
        if (registerByPlugin) {
            logger.info(TAG, "Load router map by arouter-auto-register plugin.");
        } else {
            // ..省略代码
            // 获取routeMap后,根据路由类型注册到对应的分组里
            for (String className : routerMap) {
                if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_ROOT)) {
                    // 7.加载root,类名以SUFFIX_ROOT(com.alibaba.android.arouter.routes.ARouter$$Root)开头
                    // 以<String,Class>添加到HashMap(Warehouse.groupsIndex)中
                    ((IRouteRoot) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.groupsIndex);
                } else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_INTERCEPTORS)) {
                    // 8.加载interceptorMeta,类名以SUFFIX_INTERCEPTORS(com.alibaba.android.arouter.routes.ARouter$$Interceptors)开头
                    // 以<String,IInterceptorGroup>添加到UniqueKeyTreeMap(Warehouse.interceptorsIndex)中;以树形结构实现顺序拦截
                    ((IInterceptorGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.interceptorsIndex);
                } else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_PROVIDERS)) {
                    // 9.加载providerIndex,类名以SUFFIX_PROVIDERS(com.alibaba.android.arouter.routes.ARouter$$Providers)开头
                    // 以<String,IProviderGroup>添加到HashMap(Warehouse.providersIndex)中
                    ((IProviderGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.providersIndex);
                }
            }
        }
    }
}

  此外,在ARouter的初始化过程中,本次需要额外关注下afterInit()方法,里面返回了InterceptorService的单实例对象。。

/**
 * ARouter门面
 */
public final class ARouter {
    private volatile static boolean hasInit = false;
    private static InterceptorService interceptorService;

    public static void init(Application application) {
        if (!hasInit) {
            // ARouter是门面模式,代码实现在_ARouter中,下面接着分析_ARouter
            hasInit = _ARouter.init(application);
            if (hasInit) {
                _ARouter.afterInit();
            }
        }
    }
    
    static void afterInit() {
        interceptorService = (InterceptorService) ARouter.getInstance().build("/arouter/service/interceptor").navigation();
    }
}

3.3 初始化InterceptorService

  InterceptorService是ARouter提供的拦截服务,用来管理开发者自定义的拦截器。
InterceptorService继承了IProvider(IProvider的分析见"组件化基础ARouter(二)"),在获取InterceptorService实例时会执行其init方法:

@Route(path = "/arouter/service/interceptor")
public class InterceptorServiceImpl implements InterceptorService {
    private static boolean interceptorHasInit;
    private static final Object interceptorInitLock = new Object();

    @Override
    public void init(final Context context) {
        // 在子线程初始化避免anr
        LogisticsCenter.executor.execute(new Runnable() {
            @Override
            public void run() {
                if (MapUtils.isNotEmpty(Warehouse.interceptorsIndex)) {
                    // 初始化工作比较简单:
                    // 将Warehouse.interceptorsIndex内的拦截器类分别初始化
                    // 并将实例对象放入Warehouse.interceptors中
                    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;
                    synchronized (interceptorInitLock) {
                        interceptorInitLock.notifyAll();
                    }
                }
            }
        });
    }

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

3.4 navigation拦截跳转

  根据以上分析,ARouter初始化的过程中将自定义拦截器类记录到了Warehouse.interceptorsInde中,另外初始化了总拦截器的实例对象InterceptorServiceImpl。下面继续分析在跳转过程中是如何调用到InterceptorServiceImpl中的。

final class _ARouter {
    /**
     * 执行路由流程,主要工作包括:预处理、完善路由信息、拦截、继续执行路由流程
     */
    protected Object navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
        // ...省略部分代码
        // Postcard是否是绿色通道,是则继续执行_navigation;
        // 只有RouteType是PROVIDER或FRAGMENT的Postcard才不拦截
        // 不是则执行interceptorService判断是否有拦截流程,本次暂不分析拦截流程;
        if (!postcard.isGreenChannel()) {
            // 调用到了总拦截器InterceptorServiceImpl中
            interceptorService.doInterceptions(postcard, new InterceptorCallback() {
                @Override
                public void onContinue(Postcard postcard) {
                    _navigation(postcard, requestCode, callback);
                }
                
                @Override
                public void onInterrupt(Throwable exception) {
                    if (null != callback) {
                        callback.onInterrupt(postcard);
                    }
                }
            });
        } else {
            // 4.继续执行_navigation流程
            return _navigation(postcard, requestCode, callback);
        }
        return null;
    }
}

3.5 InterceptorServiceImpl

  InterceptorService在独立线程中执行了顺序拦截的过程,独立线程是为了避免拦截耗时过长导致anr,源码实现如下:

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

    @Override
    public void doInterceptions(final Postcard postcard, final InterceptorCallback callback) {
        if (MapUtils.isNotEmpty(Warehouse.interceptorsIndex)) {
            checkInterceptorsInitStatus();
            if (!interceptorHasInit) {
                callback.onInterrupt(new HandlerException("Interceptors initialization takes too much time."));
                return;
            }
            LogisticsCenter.executor.execute(new Runnable() {
                @Override
                public void run() {
                    CancelableCountDownLatch interceptorCounter = new CancelableCountDownLatch(Warehouse.interceptors.size());
                    try {
                        _execute(0, interceptorCounter, postcard);
                        // 自定义的Interceptors中某个拦截器可能会在其他线程执行,这里需要等待至超时
                        interceptorCounter.await(postcard.getTimeout(), TimeUnit.SECONDS);
                        if (interceptorCounter.getCount() > 0) {    // Cancel the navigation this time, if it hasn't return anythings.
                            // 如果自定义的Interceptors中某个拦截器onContinue()和onInterrupt()都没执行,则CountDownLatch>0,这里返回拦截回调
                            callback.onInterrupt(new HandlerException("The interceptor processing timed out."));
                        } else if (null != postcard.getTag()) {    // Maybe some exception in the tag.
                            // 如果自定义的Interceptors中某个拦截器执行了onInterrupt(),这里返回拦截回调
                            callback.onInterrupt((Throwable) postcard.getTag());
                        } else {
                            // 其他情况下,继续跳转逻辑
                            callback.onContinue(postcard);
                        }
                    } catch (Exception e) {
                        // await()超时则会抛异常
                        callback.onInterrupt(e);
                    }
                }
            });
        } else {
            callback.onContinue(postcard);
        }
    }

    /**
     * 递归执行自定义拦截器的拦截逻辑;
     * 拦截器执行结束后onContinue()或onInterrupt()都会使CountDownLatch减一
     */
    private static void _execute(final int index, final CancelableCountDownLatch counter, final Postcard postcard) {
        if (index < Warehouse.interceptors.size()) {
            // 根据index递增,按顺序执行自定义拦截器
            IInterceptor iInterceptor = Warehouse.interceptors.get(index);
            iInterceptor.process(postcard, new InterceptorCallback() {
                @Override
                public void onContinue(Postcard postcard) {
                    counter.countDown();
                    _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) {
                    postcard.setTag(null == exception ? new HandlerException("No message.") : exception);    // save the exception message for backup.
                    counter.cancel();
                }
            });
        }
    }
}

The End

欢迎关注我,一起解锁更多技能:BC的掘金主页~💐 BC的CSDN主页~💐💐
请添加图片描述

ARouter官方文档:https://github.com/alibaba/ARouter/blob/master/README_CN.md

组件化ARouter系列(一、启动Activity):https://juejin.cn/post/7048527567346728990/

组件化ARouter系列(二、跨模块API调用):https://juejin.cn/post/7053399480191680520/

组件化ARouter系列(三、参数传递与解析):https://juejin.cn/post/7053405921065566244/

组件化ARouter系列(四、拦截器):https://juejin.cn/post/7053749555892289572/

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值