Arouter源码系列之拦截器原理详解

编程入门 行业动态 更新时间:2024-10-08 13:36:48

Arouter源码系列之拦截器原理<a href=https://www.elefans.com/category/jswz/34/1770044.html style=详解"/>

Arouter源码系列之拦截器原理详解

做过组件化开发的小伙伴应该都比较了解Arouter使用,那么Arouter的拦截器就更不用说了,一般用拦截器作用很多,比如在跳转之前做一些额外的操作(经典用法检查是否登陆,没登陆跳到登陆界面,实现一个拦截器也很简单,加一条注解就ok:@Interceptor(priority = 7),priority代表的是优先级。既然加一条注解以后每次跳转都会回调到process方法,如下:

public class Interceptor1 implements IInterceptor {/*** The operation of this interceptor.** @param postcard meta* @param callback cb*/@Overridepublic void process(final Postcard postcard, final InterceptorCallback callback){}

那么肯定有用编译期注解的方式,如下:

 annotationProcessor 'com.google.auto.service:auto-service:1.0-rc7'compileOnly 'com.google.auto.service:auto-service-annotations:1.0-rc7'

在android中如果想在编译期根据注解产生新的java类的话,那么:auto-service是必不可少,然后你继承AbstractProcessor即可实现效果,这里处理拦截器的注解类的是InterceptorProcessor,实现处理的方法如下:

  public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {if (CollectionUtils.isNotEmpty(annotations)) {Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(Interceptor.class);try {parseInterceptors(elements);} catch (Exception e) {logger.error(e);}return true;}return false;}

真正实现方法如下:

 private void parseInterceptors(Set<? extends Element> elements) throws IOException {if (CollectionUtils.isNotEmpty(elements)) {logger.info(">>> Found interceptors, size is " + elements.size() + " <<<");// Verify and cache, sort incidentally.for (Element element : elements) {if (verify(element)) {  // Check the interceptor metalogger.info("A interceptor verify over, its " + element.asType());Interceptor interceptor = element.getAnnotation(Interceptor.class);Element lastInterceptor = interceptors.get(interceptor.priority());if (null != lastInterceptor) { // Added, throw exceptionsthrow 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());}}// Interface of ARouter.TypeElement type_IInterceptor = elementUtils.getTypeElement(IINTERCEPTOR);TypeElement type_IInterceptorGroup = elementUtils.getTypeElement(IINTERCEPTOR_GROUP);/***  Build input type, format as :**  ```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_IInterceptor))));// Build input param name.ParameterSpec tollgateParamSpec = ParameterSpec.builder(inputMapTypeOfTollgate, "interceptors").build();// Build method : 'loadInto'MethodSpec.Builder loadIntoMethodOfTollgateBuilder = MethodSpec.methodBuilder(METHOD_LOAD_INTO).addAnnotation(Override.class).addModifiers(PUBLIC).addParameter(tollgateParamSpec);// Generateif (null != interceptors && interceptors.size() > 0) {// Build method bodyfor (Map.Entry<Integer, Element> entry : interceptors.entrySet()) {loadIntoMethodOfTollgateBuilder.addStatement("interceptors.put(" + entry.getKey() + ", $T.class)", ClassName.get((TypeElement) entry.getValue()));}}// Write to disk(Write file even interceptors is empty.)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_IInterceptorGroup)).build()).build().writeTo(mFiler);logger.info(">>> Interceptor group write over. <<<");}}

这个方法的意思就是取得Interceptor所有注解,然后将优先级做为key,存到TreeMap中,因为key是Integer类型实现了Comparable接口,所以TreeMap是根据优先级排序的,排好序之后,用javaPoet去生成ARouter$$Interceptors$$(MoudleName)类,这个类主要用来保存拦截器的真正实现类的class,如这句代码:loadIntoMethodOfTollgateBuilder.addStatement("interceptors.put(" + entry.getKey() + ",$T.class)", ClassName.get((TypeElement) entry.getValue()));

,JavaPoet链接。

这个拦截器什么时候被使用的呢?当然是在跳转前面,如下:

 */protected Object navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {//去掉无用代码......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*/@Overridepublic void onContinue(Postcard postcard) {_navigation(postcard, requestCode, callback);}/*** Interrupt process, pipeline will be destory when this method called.** @param exception Reson of interrupt.*/@Overridepublic 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;}

可以看到最终拦截器是通过interceptorService类调用,它的实现类是InterceptorServiceImpl,实现方法如下:

 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() {@Overridepublic void run() {CancelableCountDownLatch interceptorCounter = new CancelableCountDownLatch(Warehouse.interceptors.size());try {_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);}}
 private static void _execute(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() {@Overridepublic void onContinue(Postcard postcard) {// Last interceptor excute over with no exception.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.}@Overridepublic void onInterrupt(Throwable exception) {// Last interceptor execute over with fatal exception.postcard.setTag(null == exception ? new HandlerException("No message.") : exception);    // save the exception message for backup.counter.cancel();// Be attention, maybe the thread in callback has been changed,// then the catch block(L207) will be invalid.// The worst is the thread changed to main thread, then the app will be crash, if you throw this exception!
//                    if (!Looper.getMainLooper().equals(Looper.myLooper())) {    // You shouldn't throw the exception if the thread is main thread.
//                        throw new HandlerException(exception.getMessage());
//                    }}});}}

_execute在内部一直调用的递归,遍历map中所有而编译器用的TreeMap保证了优先级,好了到这拦截器的流程基本走完了,那么还有一个问题就是拦截器的集合 Warehouse.interceptors是什么时候填充的呢?当然是ARouter初始化的时候,如下:

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

最终调用LogisticsCenter.init,如下:

  public synchronized static void init(Context context, ThreadPoolExecutor tpe) throws HandlerException {mContext = context;executor = tpe;try {long startInit = System.currentTimeMillis();//load by plugin firstloadRouterMap();if (registerByPlugin) {logger.info(TAG, "Load router map by arouter-auto-register plugin.");} else {Set<String> routerMap;// It will rebuild router map every times when debuggable.if (ARouter.debuggable() || PackageUtils.isNewVersion(context)) {logger.info(TAG, "Run with debug mode or new install, rebuild router map.");// These class was generated by arouter-compiler.routerMap = ClassUtils.getFileNameByPackageName(mContext, ROUTE_ROOT_PAKCAGE);if (!routerMap.isEmpty()) {context.getSharedPreferences(AROUTER_SP_CACHE_KEY, Context.MODE_PRIVATE).edit().putStringSet(AROUTER_SP_KEY_MAP, routerMap).apply();}PackageUtils.updateVersion(context);    // Save new version name when router map update finishes.} else {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();for (String className : routerMap) {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)) {// Load interceptorMeta((IInterceptorGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.interceptorsIndex);} else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_PROVIDERS)) {// Load providerIndex((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!");}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() + "]");}}

其中getFileNameByPackageName真正实现了class的储存加载,如下代码:

 public static Set<String> getFileNameByPackageName(Context context, final String packageName) throws PackageManager.NameNotFoundException, IOException, InterruptedException {final Set<String> classNames = new HashSet<>();List<String> paths = getSourcePaths(context);final CountDownLatch parserCtl = new CountDownLatch(paths.size());for (final String path : paths) {DefaultPoolExecutor.getInstance().execute(new Runnable() {@Overridepublic void run() {DexFile dexfile = null;try {if (path.endsWith(EXTRACTED_SUFFIX)) {//NOT use new DexFile(path), because it will throw "permission error in /data/dalvik-cache"dexfile = DexFile.loadDex(path, path + ".tmp", 0);} else {dexfile = new DexFile(path);}Enumeration<String> dexEntries = dexfile.entries();while (dexEntries.hasMoreElements()) {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) {}}parserCtl.countDown();}}});}parserCtl.await();Log.d(Consts.TAG, "Filter " + classNames.size() + " classes by packageName <" + packageName + ">");return classNames;}

用的DexFile加载所有编译期产生的新java类的class,然后将class反射实例化,实现如下:

 for (String className : routerMap) {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)) {// Load interceptorMeta((IInterceptorGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.interceptorsIndex);} else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_PROVIDERS)) {// Load providerIndex((IProviderGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.providersIndex);}}}

最终InterceptorServiceImpl的init方法实现,如下:

 public void init(final Context context) {LogisticsCenter.executor.execute(new Runnable() {@Overridepublic 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();}}}});}

更多推荐

Arouter源码系列之拦截器原理详解

本文发布于:2024-02-11 22:35:38,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1683976.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:详解   源码   原理   拦截器   系列之

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!