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


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

    1. public class Interceptor1 implements IInterceptor {
    2. /**
    3. * The operation of this interceptor.
    4. *
    5. * @param postcard meta
    6. * @param callback cb
    7. */
    8. @Override
    9. public void process(final Postcard postcard, final InterceptorCallback callback){}

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

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

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

    1. public boolean process(Set annotations, RoundEnvironment roundEnv) {
    2. if (CollectionUtils.isNotEmpty(annotations)) {
    3. Setextends Element> elements = roundEnv.getElementsAnnotatedWith(Interceptor.class);
    4. try {
    5. parseInterceptors(elements);
    6. } catch (Exception e) {
    7. logger.error(e);
    8. }
    9. return true;
    10. }
    11. return false;
    12. }

    真正实现方法如下:

    1. private void parseInterceptors(Set elements) throws IOException {
    2. if (CollectionUtils.isNotEmpty(elements)) {
    3. logger.info(">>> Found interceptors, size is " + elements.size() + " <<<");
    4. // Verify and cache, sort incidentally.
    5. for (Element element : elements) {
    6. if (verify(element)) { // Check the interceptor meta
    7. logger.info("A interceptor verify over, its " + element.asType());
    8. Interceptor interceptor = element.getAnnotation(Interceptor.class);
    9. Element lastInterceptor = interceptors.get(interceptor.priority());
    10. if (null != lastInterceptor) { // Added, throw exceptions
    11. throw new IllegalArgumentException(
    12. String.format(Locale.getDefault(), "More than one interceptors use same priority [%d], They are [%s] and [%s].",
    13. interceptor.priority(),
    14. lastInterceptor.getSimpleName(),
    15. element.getSimpleName())
    16. );
    17. }
    18. interceptors.put(interceptor.priority(), element);
    19. } else {
    20. logger.error("A interceptor verify failed, its " + element.asType());
    21. }
    22. }
    23. // Interface of ARouter.
    24. TypeElement type_IInterceptor = elementUtils.getTypeElement(IINTERCEPTOR);
    25. TypeElement type_IInterceptorGroup = elementUtils.getTypeElement(IINTERCEPTOR_GROUP);
    26. /**
    27. * Build input type, format as :
    28. *
    29. * ```Map>```
    30. */
    31. ParameterizedTypeName inputMapTypeOfTollgate = ParameterizedTypeName.get(
    32. ClassName.get(Map.class),
    33. ClassName.get(Integer.class),
    34. ParameterizedTypeName.get(
    35. ClassName.get(Class.class),
    36. WildcardTypeName.subtypeOf(ClassName.get(type_IInterceptor))
    37. )
    38. );
    39. // Build input param name.
    40. ParameterSpec tollgateParamSpec = ParameterSpec.builder(inputMapTypeOfTollgate, "interceptors").build();
    41. // Build method : 'loadInto'
    42. MethodSpec.Builder loadIntoMethodOfTollgateBuilder = MethodSpec.methodBuilder(METHOD_LOAD_INTO)
    43. .addAnnotation(Override.class)
    44. .addModifiers(PUBLIC)
    45. .addParameter(tollgateParamSpec);
    46. // Generate
    47. if (null != interceptors && interceptors.size() > 0) {
    48. // Build method body
    49. for (Map.Entry entry : interceptors.entrySet()) {
    50. loadIntoMethodOfTollgateBuilder.addStatement("interceptors.put(" + entry.getKey() + ", $T.class)", ClassName.get((TypeElement) entry.getValue()));
    51. }
    52. }
    53. // Write to disk(Write file even interceptors is empty.)
    54. JavaFile.builder(PACKAGE_OF_GENERATE_FILE,
    55. TypeSpec.classBuilder(NAME_OF_INTERCEPTOR + SEPARATOR + moduleName)
    56. .addModifiers(PUBLIC)
    57. .addJavadoc(WARNING_TIPS)
    58. .addMethod(loadIntoMethodOfTollgateBuilder.build())
    59. .addSuperinterface(ClassName.get(type_IInterceptorGroup))
    60. .build()
    61. ).build().writeTo(mFiler);
    62. logger.info(">>> Interceptor group write over. <<<");
    63. }
    64. }

    这个方法的意思就是取得Interceptor所有注解,然后将优先级做为key,存到TreeMap中,因为keyInteger类型实现了Comparable接口,所以TreeMap是根据优先级排序的,排好序之后,用javaPoet去生成ARouter

    Interceptors" role="presentation" style="text-align: center; position: relative;">Interceptors
    (MoudleName)类,这个类主要用来保存拦截器的真正实现类的class,如这句代码:loadIntoMethodOfTollgateBuilder.addStatement("interceptors.put(" + entry.getKey() + ",$T.class)", ClassName.get((TypeElement) entry.getValue()));

    JavaPoet链接

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

    1. */
    2. protected Object navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
    3. //去掉无用代码......
    4. if (!postcard.isGreenChannel()) { // It must be run in async thread, maybe interceptor cost too mush time made ANR.
    5. interceptorService.doInterceptions(postcard, new InterceptorCallback() {
    6. /**
    7. * Continue process
    8. *
    9. * @param postcard route meta
    10. */
    11. @Override
    12. public void onContinue(Postcard postcard) {
    13. _navigation(postcard, requestCode, callback);
    14. }
    15. /**
    16. * Interrupt process, pipeline will be destory when this method called.
    17. *
    18. * @param exception Reson of interrupt.
    19. */
    20. @Override
    21. public void onInterrupt(Throwable exception) {
    22. if (null != callback) {
    23. callback.onInterrupt(postcard);
    24. }
    25. logger.info(Consts.TAG, "Navigation failed, termination by interceptor : " + exception.getMessage());
    26. }
    27. });
    28. } else {
    29. return _navigation(postcard, requestCode, callback);
    30. }
    31. return null;
    32. }

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

    1. public void doInterceptions(final Postcard postcard, final InterceptorCallback callback) {
    2. if (MapUtils.isNotEmpty(Warehouse.interceptorsIndex)) {
    3. checkInterceptorsInitStatus();
    4. if (!interceptorHasInit) {
    5. callback.onInterrupt(new HandlerException("Interceptors initialization takes too much time."));
    6. return;
    7. }
    8. LogisticsCenter.executor.execute(new Runnable() {
    9. @Override
    10. public void run() {
    11. CancelableCountDownLatch interceptorCounter = new CancelableCountDownLatch(Warehouse.interceptors.size());
    12. try {
    13. _execute(0, interceptorCounter, postcard);
    14. interceptorCounter.await(postcard.getTimeout(), TimeUnit.SECONDS);
    15. if (interceptorCounter.getCount() > 0) { // Cancel the navigation this time, if it hasn't return anythings.
    16. callback.onInterrupt(new HandlerException("The interceptor processing timed out."));
    17. } else if (null != postcard.getTag()) { // Maybe some exception in the tag.
    18. callback.onInterrupt((Throwable) postcard.getTag());
    19. } else {
    20. callback.onContinue(postcard);
    21. }
    22. } catch (Exception e) {
    23. callback.onInterrupt(e);
    24. }
    25. }
    26. });
    27. } else {
    28. callback.onContinue(postcard);
    29. }
    30. }
    1. private static void _execute(final int index, final CancelableCountDownLatch counter, final Postcard postcard) {
    2. if (index < Warehouse.interceptors.size()) {
    3. IInterceptor iInterceptor = Warehouse.interceptors.get(index);
    4. iInterceptor.process(postcard, new InterceptorCallback() {
    5. @Override
    6. public void onContinue(Postcard postcard) {
    7. // Last interceptor excute over with no exception.
    8. counter.countDown();
    9. _execute(index + 1, counter, postcard); // When counter is down, it will be execute continue ,but index bigger than interceptors size, then U know.
    10. }
    11. @Override
    12. public void onInterrupt(Throwable exception) {
    13. // Last interceptor execute over with fatal exception.
    14. postcard.setTag(null == exception ? new HandlerException("No message.") : exception); // save the exception message for backup.
    15. counter.cancel();
    16. // Be attention, maybe the thread in callback has been changed,
    17. // then the catch block(L207) will be invalid.
    18. // The worst is the thread changed to main thread, then the app will be crash, if you throw this exception!
    19. // if (!Looper.getMainLooper().equals(Looper.myLooper())) { // You shouldn't throw the exception if the thread is main thread.
    20. // throw new HandlerException(exception.getMessage());
    21. // }
    22. }
    23. });
    24. }
    25. }

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

    1. protected static synchronized boolean init(Application application) {
    2. mContext = application;
    3. LogisticsCenter.init(mContext, executor);
    4. logger.info(Consts.TAG, "ARouter init success!");
    5. hasInit = true;
    6. mHandler = new Handler(Looper.getMainLooper());
    7. return true;
    8. }

    最终调用LogisticsCenter.init,如下:

    1. public synchronized static void init(Context context, ThreadPoolExecutor tpe) throws HandlerException {
    2. mContext = context;
    3. executor = tpe;
    4. try {
    5. long startInit = System.currentTimeMillis();
    6. //load by plugin first
    7. loadRouterMap();
    8. if (registerByPlugin) {
    9. logger.info(TAG, "Load router map by arouter-auto-register plugin.");
    10. } else {
    11. Set routerMap;
    12. // It will rebuild router map every times when debuggable.
    13. if (ARouter.debuggable() || PackageUtils.isNewVersion(context)) {
    14. logger.info(TAG, "Run with debug mode or new install, rebuild router map.");
    15. // These class was generated by arouter-compiler.
    16. routerMap = ClassUtils.getFileNameByPackageName(mContext, ROUTE_ROOT_PAKCAGE);
    17. if (!routerMap.isEmpty()) {
    18. context.getSharedPreferences(AROUTER_SP_CACHE_KEY, Context.MODE_PRIVATE).edit().putStringSet(AROUTER_SP_KEY_MAP, routerMap).apply();
    19. }
    20. PackageUtils.updateVersion(context); // Save new version name when router map update finishes.
    21. } else {
    22. logger.info(TAG, "Load router map from cache.");
    23. routerMap = new HashSet<>(context.getSharedPreferences(AROUTER_SP_CACHE_KEY, Context.MODE_PRIVATE).getStringSet(AROUTER_SP_KEY_MAP, new HashSet()));
    24. }
    25. logger.info(TAG, "Find router map finished, map size = " + routerMap.size() + ", cost " + (System.currentTimeMillis() - startInit) + " ms.");
    26. startInit = System.currentTimeMillis();
    27. for (String className : routerMap) {
    28. if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_ROOT)) {
    29. // This one of root elements, load root.
    30. ((IRouteRoot) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.groupsIndex);
    31. } else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_INTERCEPTORS)) {
    32. // Load interceptorMeta
    33. ((IInterceptorGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.interceptorsIndex);
    34. } else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_PROVIDERS)) {
    35. // Load providerIndex
    36. ((IProviderGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.providersIndex);
    37. }
    38. }
    39. }
    40. logger.info(TAG, "Load root element finished, cost " + (System.currentTimeMillis() - startInit) + " ms.");
    41. if (Warehouse.groupsIndex.size() == 0) {
    42. logger.error(TAG, "No mapping files were found, check your configuration please!");
    43. }
    44. if (ARouter.debuggable()) {
    45. 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()));
    46. }
    47. } catch (Exception e) {
    48. throw new HandlerException(TAG + "ARouter init logistics center exception! [" + e.getMessage() + "]");
    49. }
    50. }

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

    1. public static Set getFileNameByPackageName(Context context, final String packageName) throws PackageManager.NameNotFoundException, IOException, InterruptedException {
    2. final Set classNames = new HashSet<>();
    3. List paths = getSourcePaths(context);
    4. final CountDownLatch parserCtl = new CountDownLatch(paths.size());
    5. for (final String path : paths) {
    6. DefaultPoolExecutor.getInstance().execute(new Runnable() {
    7. @Override
    8. public void run() {
    9. DexFile dexfile = null;
    10. try {
    11. if (path.endsWith(EXTRACTED_SUFFIX)) {
    12. //NOT use new DexFile(path), because it will throw "permission error in /data/dalvik-cache"
    13. dexfile = DexFile.loadDex(path, path + ".tmp", 0);
    14. } else {
    15. dexfile = new DexFile(path);
    16. }
    17. Enumeration dexEntries = dexfile.entries();
    18. while (dexEntries.hasMoreElements()) {
    19. String className = dexEntries.nextElement();
    20. if (className.startsWith(packageName)) {
    21. classNames.add(className);
    22. }
    23. }
    24. } catch (Throwable ignore) {
    25. Log.e("ARouter", "Scan map file in dex files made error.", ignore);
    26. } finally {
    27. if (null != dexfile) {
    28. try {
    29. dexfile.close();
    30. } catch (Throwable ignore) {
    31. }
    32. }
    33. parserCtl.countDown();
    34. }
    35. }
    36. });
    37. }
    38. parserCtl.await();
    39. Log.d(Consts.TAG, "Filter " + classNames.size() + " classes by packageName <" + packageName + ">");
    40. return classNames;
    41. }

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

    1. for (String className : routerMap) {
    2. if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_ROOT)) {
    3. // This one of root elements, load root.
    4. ((IRouteRoot) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.groupsIndex);
    5. } else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_INTERCEPTORS)) {
    6. // Load interceptorMeta
    7. ((IInterceptorGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.interceptorsIndex);
    8. } else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_PROVIDERS)) {
    9. // Load providerIndex
    10. ((IProviderGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.providersIndex);
    11. }
    12. }
    13. }

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

    1. public void init(final Context context) {
    2. LogisticsCenter.executor.execute(new Runnable() {
    3. @Override
    4. public void run() {
    5. if (MapUtils.isNotEmpty(Warehouse.interceptorsIndex)) {
    6. for (Map.Entryextends IInterceptor>> entry : Warehouse.interceptorsIndex.entrySet()) {
    7. Classextends IInterceptor> interceptorClass = entry.getValue();
    8. try {
    9. IInterceptor iInterceptor = interceptorClass.getConstructor().newInstance();
    10. iInterceptor.init(context);
    11. Warehouse.interceptors.add(iInterceptor);
    12. } catch (Exception ex) {
    13. throw new HandlerException(TAG + "ARouter init interceptor error! name = [" + interceptorClass.getName() + "], reason = [" + ex.getMessage() + "]");
    14. }
    15. }
    16. interceptorHasInit = true;
    17. logger.info(TAG, "ARouter interceptors init over.");
    18. synchronized (interceptorInitLock) {
    19. interceptorInitLock.notifyAll();
    20. }
    21. }
    22. }
    23. });
    24. }

  • 相关阅读:
    【​毕业季·进击的技术er​】--分享自己的经历与经验
    视频怎么添加水印?快来收好这些方法
    【操作系统】操作系统基础必知必会
    win10下用cmake编译zlib并验证
    HTML-CSS休闲小游戏--渣灰的愿望之Can-Can渣灰哥
    RISCV汇编指令
    低耦合的理解
    docker-pinpoint-docker链路追踪
    抗击疫情静态HTML网页作业作品 大学生抗疫感动专题页设计制作成品 简单DIV CSS布局网站
    P2922 [USACO08DEC]Secret Message G
  • 原文地址:https://blog.csdn.net/xiatiandefeiyu/article/details/126645599