当前位置: 首页>数据库>正文

ARouter源码分析下

  • 概述

    书接上回:ARouter源码分析上

  • 其他总结

    如果代码中使用了Route注解,build生成的类有两种情况,一种是非IProvider,会自动生成名为ARouter$$Root$$(ModuleName模块名)的class,比如Activity、Fragment这些类上的Route注解,这个class只会有一个,针对这种情况还会生成对应的名为ARouter$$Group$$(groupName路由最前面两个‘/’之间的string)的class,这个会有多个,ARouter$$Root$$(ModuleName模块名)的loadInto用于put这些class,实际就是Warehouse.groupsIndex里保存的就是这些class。譬如:

    public class ARouter$$Root$$app implements IRouteRoot {
      @Override
      public void loadInto(Map<String, Class<extends IRouteGroup>> routes) {
        routes.put("john", ARouter$$Group$$john.class);
        routes.put("lllSS", ARouter$$Group$$lllSS.class);
        routes.put("rxseriers", ARouter$$Group$$rxseriers.class);
      }
    }
    

    另一种是实现自IProvider的类,会自动生成名为ARouter$$Providers$$(ModuleName模块名)的class,Warehouse.providersIndex里保存的就是这些class。譬如:

    public class ARouter$$Providers$$app implements IProviderGroup {
      @Override
      public void loadInto(Map<String, RouteMeta> providers) {
        providers.put("com.alibaba.android.arouter.facade.service.PretreatmentService", RouteMeta.build(RouteType.PROVIDER, MyPreTreatmentService.class, "/lllSS/myPretreatmentService", "lllSS", null, 1, -2147483648));
        providers.put("com.mph.rxseriers.arouter.MyLogicService", RouteMeta.build(RouteType.PROVIDER, MyLogicServiceImpl.class, "/lllSS/my_logic_service", "lllSS", null, -1, -2147483648));
        providers.put("com.alibaba.android.arouter.facade.service.PathReplaceService", RouteMeta.build(RouteType.PROVIDER, MyPathReplaceService.class, "/lllSS/my_replace_path_service", "lllSS", null, -1, -2147483648));
        providers.put("com.alibaba.android.arouter.facade.service.PretreatmentService", RouteMeta.build(RouteType.PROVIDER, OtherPretreatmentService.class, "/lllSS/otherPretreatmentService", "lllSS", null, 2, -2147483648));
        providers.put("com.alibaba.android.arouter.facade.service.PathReplaceService", RouteMeta.build(RouteType.PROVIDER, OtherPathReplaceService.class, "/lllSS/other_replace_path_service", "lllSS", null, -1, -2147483648));
      }
    }
    

    如果代码中使用了Interceptor注解且实现自IInterceptor的类,则会自动生成名为ARouter$$Interceptors$$(ModuleName模块名)的class,Warehouse.interceptorsIndex里保存的就是这些class。譬如:

    public class ARouter$$Interceptors$$app implements IInterceptorGroup {
      @Override
      public void loadInto(Map<Integer, Class<extends IInterceptor>> interceptors) {
        interceptors.put(5, LoginInterceptor.class);
      }
    }
    
  • What is ARouter

    A framework for assisting in the renovation of Android app componentization
    

    官方介绍只有简短的一句话总结,一套协助Android app组件化革新的框架。

  • Why to use it

    即便是组件化,我们其实也可以用startActivity的方式来进行跳转,那ARouter又有何特殊之处呢?

    1. 传统的方式,不管是url传参还是bundle传参都较为繁琐,需要把参数先设置到url或者bundle,然后在跳转页通过getIntent方法来获取传参,ARouter把这部分工作封装了起来。通过链式调用设置参数,通过Autowired注解获取参数。
    2. 支持组件化,ARouter的组件化在于不需要module间相互依赖就可以互相跳转。
    3. 支持interceptor,可以通过interceptor做一些跳转前的逻辑,比如需要登录状态才能访问的页面。
    4. 支持依赖注入,通过AOP思想把注解映射成路由类。
    5. 支持Instant Run方式编译的apk。
    6. 支持MultiDex。
    7. 支持业务类Service,通过Route和Autowired注解和Service类联系起来。
  • How to use it & Why it work

    • 依赖

      每一个需要使用ARouter的module下的build.gradle中添加下面配置(可以把这块抽出来,通过apply from: '../xx.gradle'去引用):

      //如果含有kotlin代码,需要添加一下kotlin plugin
      apply plugin: 'kotlin-kapt'
      
      //使用ARouter auto-register自动扫描路由信息,非必选,通过gradle注册,性能更优
      apply plugin: 'com.alibaba.arouter'
      
      android {
          defaultConfig {
              ...
              javaCompileOptions {
                  annotationProcessorOptions {
                      arguments = [AROUTER_MODULE_NAME: project.getName()]
                  }
              }
          }
      }
      
      //使用ARouter auto-register自动扫描路由信息,非必选,通过gradle注册,性能更优
      buildscript {
          repositories {
              jcenter()
          }
      
          dependencies {
              //ARouter auto-register,Replace with the latest versios
              classpath "com.alibaba:arouter-register:?"
          }
      }
      
      //如果是包含kotlin类的路由,需要添加下面代码
      kapt {
          arguments {
              arg("AROUTER_MODULE_NAME", project.getName())
          }
      }
      
      dependencies {
          // Replace with the latest version
          //使用api继承依赖,不需要每个module都添加
          api 'com.alibaba:arouter-api:?'
          //这个不能使用api依赖继承,所以每个module都要添加
          annotationProcessor 'com.alibaba:arouter-compiler:?'
          //如果是kotlin,需要添加kotlin编译jar包
          kapt 'com.alibaba:arouter-compiler:?'
          ...
      }
      
    • 两种加载方式

      通过Route、Autowired、Interceptor等ARouter注解修饰的类需要被加载到一些Map中,这些Map被放在Warehouse类中:

      class Warehouse {
          // Cache route and metas
          static Map<String, Class<extends IRouteGroup>> groupsIndex = new HashMap<>();
          static Map<String, RouteMeta> routes = new HashMap<>();
      
          // Cache provider
          static Map<Class, IProvider> providers = new HashMap<>();
          static Map<String, RouteMeta> providersIndex = new HashMap<>();
      
          // 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<>();
      
          static void clear() {
              routes.clear();
              groupsIndex.clear();
              providers.clear();
              providersIndex.clear();
              interceptors.clear();
              interceptorsIndex.clear();
          }
      }
      

      加载的方式有两种,一种是通过plugin来利用gradle的构建自动生成加载代码的方式加载,另一种是通过java代码手动扫描出含有这些注解的类,这也是初始化的两种方式,官方提倡使用register-plugin自动生成性能更好。

    • 初始化

      class MyApp : MultiDexApplication() {
      
          override fun onCreate() {
              super.onCreate()
              if(BuildConfig.DEBUG){
                  ARouter.openLog()
                  ARouter.openDebug()
              }
              ARouter.init(this)
          }
        
          override fun onTerminate() {
              super.onTerminate()
              ARouter.getInstance().destroy()
          }
      }
      

      为了app安全考虑,非debug版本下不要输出log和打开debug模式。

      看一下ARouter的init方法做了什么:

      /**
       * Init, it must be call before used router.
       */
      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的类去做的,Arouter只是它的包装类。__ARouter的init方法返回hasInit标志是否已成功初始化:

      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方法:

      /**
       * LogisticsCenter init, load all metas in memory. Demand initialization
       */
      public synchronized static void init(Context context, ThreadPoolExecutor tpe) throws HandlerException {
          mContext = context;
          executor = tpe;
      
          try {
              long startInit = System.currentTimeMillis();
              //billy.qi modified at 2017-12-06
              //load by plugin first
              loadRouterMap();
              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() + "]");
          }
      }
      

      首先调用了loadRouterMap()方法来试图通过arouter-auto-register plugin加载route信息:

      /**
       * arouter-auto-register plugin will generate code inside this method
       * call this method to register all Routers, Interceptors and Providers
       * @author billy.qi <a href="mailto:qiyilike@163.com">Contact me.</a>
       * @since 2017-12-06
       */
      private static void loadRouterMap() {
          registerByPlugin = false;
          //auto generate register code by gradle plugin: arouter-auto-register
          // looks like below:
          // registerRouteRoot(new ARouter..Root..modulejava());
          // registerRouteRoot(new ARouter..Root..modulekotlin());
      }
      

      这个方法里什么也没有,因为这里要交给plugin去注册,默认设置为不通过plugin注册,通过注释可以得知,plugin会把加载逻辑代码动态的生成在这个方法里,至于怎样生成的另起篇文章总结。

      下载arouter-register的source包查看:

      class RouteMethodVisitor extends MethodVisitor {
      
          RouteMethodVisitor(int api, MethodVisitor mv) {
              super(api, mv)
          }
      
          @Override
          void visitInsn(int opcode) {
              //generate code before return
              if ((opcode >= Opcodes.IRETURN && opcode <= Opcodes.RETURN)) {
                  extension.classList.each { name ->
                      name = name.replaceAll("/", ".")
                      mv.visitLdcInsn(name)//类名
                      // generate invoke register method into LogisticsCenter.loadRouterMap()
                      mv.visitMethodInsn(Opcodes.INVOKESTATIC
                              , ScanSetting.GENERATE_TO_CLASS_NAME
                              , ScanSetting.REGISTER_METHOD_NAME
                              , "(Ljava/lang/String;)V"
                              , false)
                  }
              }
              super.visitInsn(opcode)
          }
          @Override
          void visitMaxs(int maxStack, int maxLocals) {
              super.visitMaxs(maxStack + 4, maxLocals)
          }
      }
      

      mv.visitMethodInsn方法就是注入加载代码的地方,采用循环的方式来加载每一个类。ScanSetting.GENERATE_TO_CLASS_NAME是:

      /**
       * The register code is generated into this class
       */
      static final String GENERATE_TO_CLASS_NAME = 'com/alibaba/android/arouter/core/LogisticsCenter'
      

      ScanSetting.REGISTER_METHOD_NAME是:

      /**
       * register method name in class: {@link #GENERATE_TO_CLASS_NAME}
       */
      static final String REGISTER_METHOD_NAME = 'register'
      

      在arouter-api的jar包中找到LogisticsCenter的register方法:

      /**
       * register by class name
       * Sacrificing a bit of efficiency to solve
       * the problem that the main dex file size is too large
       * @author billy.qi <a href="mailto:qiyilike@163.com">Contact me.</a>
       * @param className class name
       */
      private static void register(String className) {
          if (!TextUtils.isEmpty(className)) {
              try {
                  Class<?> clazz = Class.forName(className);
                  Object obj = clazz.getConstructor().newInstance();
                  if (obj instanceof IRouteRoot) {
                      registerRouteRoot((IRouteRoot) obj);
                  } else if (obj instanceof IProviderGroup) {
                      registerProvider((IProviderGroup) obj);
                  } else if (obj instanceof IInterceptorGroup) {
                      registerInterceptor((IInterceptorGroup) obj);
                  } else {
                      logger.info(TAG, "register failed, class name: " + className
                              + " should implements one of IRouteRoot/IProviderGroup/IInterceptorGroup.");
                  }
              } catch (Exception e) {
                  logger.error(TAG,"register class error:" + className);
              }
          }
      }
      

      三个方法分别是注册Root(页面)、Interceptor、IProvider(Service)的逻辑:

      /**
       * method for arouter-auto-register plugin to register Routers
       * @param routeRoot IRouteRoot implementation class in the package: com.alibaba.android.arouter.core.routers
       * @author billy.qi <a href="mailto:qiyilike@163.com">Contact me.</a>
       * @since 2017-12-06
       */
      private static void registerRouteRoot(IRouteRoot routeRoot) {
          markRegisteredByPlugin();
          if (routeRoot != null) {
              routeRoot.loadInto(Warehouse.groupsIndex);
          }
      }
      
      /**
       * method for arouter-auto-register plugin to register Interceptors
       * @param interceptorGroup IInterceptorGroup implementation class in the package: com.alibaba.android.arouter.core.routers
       * @author billy.qi <a href="mailto:qiyilike@163.com">Contact me.</a>
       * @since 2017-12-06
       */
      private static void registerInterceptor(IInterceptorGroup interceptorGroup) {
          markRegisteredByPlugin();
          if (interceptorGroup != null) {
              interceptorGroup.loadInto(Warehouse.interceptorsIndex);
          }
      }
      
      /**
       * method for arouter-auto-register plugin to register Providers
       * @param providerGroup IProviderGroup implementation class in the package: com.alibaba.android.arouter.core.routers
       * @author billy.qi <a href="mailto:qiyilike@163.com">Contact me.</a>
       * @since 2017-12-06
       */
      private static void registerProvider(IProviderGroup providerGroup) {
          markRegisteredByPlugin();
          if (providerGroup != null) {
              providerGroup.loadInto(Warehouse.providersIndex);
          }
      }
      

      markRegisteredByPlugin是修改registerByPlugin标志位:

      /**
       * mark already registered by arouter-auto-register plugin
       * @author billy.qi <a href="mailto:qiyilike@163.com">Contact me.</a>
       * @since 2017-12-06
       */
      private static void markRegisteredByPlugin() {
          if (!registerByPlugin) {
              registerByPlugin = true;
          }
      }
      

      可以看到,对于不同类型的route类分别保存到Warehouse中对应的Map中,注意这里添加的只是类,不是跳转的所有信息。

      回到init方法,判断registerByPlugin是否为true来决定是否手动加载,如果没有设置register-plugin,则会走到else分支,这里就是手动代码检索路由信息的地方:

      if (ARouter.debuggable() || PackageUtils.isNewVersion(context))判断如果是debug模式或者是新版本更新的话就重新检索路由信息,核心代码就是ClassUtils.getFileNameByPackageName(mContext, ROUTE_ROOT_PAKCAGE),ROUTE_ROOT_PACKAGE是:

      public static final String ROUTE_ROOT_PAKCAGE = "com.alibaba.android.arouter.routes";
      
      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() {
                  @Override
                  public 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;
      }
      

      这里的工作就是多线程读取所有的dex文件,逐个判断里面的className,如果className是以com.alibaba.android.arouter.routes开头的则存入classNames集合,最终会返回所有com.alibaba.android.arouter.routes开头的类名。

      注意在最前面有一个getSourcePaths方法来获取apk的所有dex文件的位置,然后从这些位置读取dex文件:

       /**
           * get all the dex path
           *
           * @param context the application context
           * @return all the dex path
           * @throws PackageManager.NameNotFoundException
           * @throws IOException
           */
          public static List<String> getSourcePaths(Context context) throws PackageManager.NameNotFoundException, IOException {
              ApplicationInfo applicationInfo = context.getPackageManager().getApplicationInfo(context.getPackageName(), 0);
              File sourceApk = new File(applicationInfo.sourceDir);
      
              List<String> sourcePaths = new ArrayList<>();
              sourcePaths.add(applicationInfo.sourceDir); //add the default apk path
      
              //the prefix of extracted file, ie: test.classes
              String extractedFilePrefix = sourceApk.getName() + EXTRACTED_NAME_EXT;
      
      //        如果VM已经支持了MultiDex,就不要去Secondary Folder加载 Classesx.zip了,那里已经么有了
      //        通过是否存在sp中的multidex.version是不准确的,因为从低版本升级上来的用户,是包含这个sp配置的
              if (!isVMMultidexCapable()) {
                  //the total dex numbers
                  int totalDexNumber = getMultiDexPreferences(context).getInt(KEY_DEX_NUMBER, 1);
                  File dexDir = new File(applicationInfo.dataDir, SECONDARY_FOLDER_NAME);
      
                  for (int secondaryNumber = 2; secondaryNumber <= totalDexNumber; secondaryNumber++) {
                      //for each dex file, ie: test.classes2.zip, test.classes3.zip...
                      String fileName = extractedFilePrefix + secondaryNumber + EXTRACTED_SUFFIX;
                      File extractedFile = new File(dexDir, fileName);
                      if (extractedFile.isFile()) {
                          sourcePaths.add(extractedFile.getAbsolutePath());
                          //we ignore the verify zip part
                      } else {
                          throw new IOException("Missing extracted secondary dex file '" + extractedFile.getPath() + "'");
                      }
                  }
              }
      
              if (ARouter.debuggable()) { // Search instant run support only debuggable
                  sourcePaths.addAll(tryLoadInstantRunDexFile(applicationInfo));
              }
              return sourcePaths;
          }
      

      isVMMultidexCapable判断是否JVM支持MultiDex了,就是判断JVM的版本(2.1之后的支持):

      /**
       * Identifies if the current VM has a native support for multidex, meaning there is no need for
       * additional installation by this library.
       *
       * @return true if the VM handles multidex
       */
      private static boolean isVMMultidexCapable() {
          boolean isMultidexCapable = false;
          String vmName = null;
      
          try {
              if (isYunOS()) {    // YunOS需要特殊判断
                  vmName = "'YunOS'";
                  isMultidexCapable = Integer.valueOf(System.getProperty("ro.build.version.sdk")) >= 21;
              } else {    // 非YunOS原生Android
                  vmName = "'Android'";
                  String versionString = System.getProperty("java.vm.version");
                  if (versionString != null) {
                      Matcher matcher = Pattern.compile("(\d+)\.(\d+)(\.\d+)?").matcher(versionString);
                      if (matcher.matches()) {
                          try {
                              int major = Integer.parseInt(matcher.group(1));
                              int minor = Integer.parseInt(matcher.group(2));
                              isMultidexCapable = (major > VM_WITH_MULTIDEX_VERSION_MAJOR)
                                      || ((major == VM_WITH_MULTIDEX_VERSION_MAJOR)
                                      && (minor >= VM_WITH_MULTIDEX_VERSION_MINOR));
                          } catch (NumberFormatException ignore) {
                              // let isMultidexCapable be false
                          }
                      }
                  }
              }
          } catch (Exception ignore) {
      
          }
      
          Log.i(Consts.TAG, "VM with name " + vmName + (isMultidexCapable " has multidex support" : " does not have multidex support"));
          return isMultidexCapable;
      }
      

      取到routeMap之后会把它存到SP中,最后遍历routeMap,根据前缀Root、Interceptors、Providers的不同分别存入Warehouse中的对应xxxIndex的map中,这一步和自动加载是一样的。

      至此,初始化工作就完成了。

    • 路由名称注意

      Route注解指定的路由path中格式为/groupName/elseName...,至少含有两个‘/’且groupName不能为空。因为ARouter会自动生成以groupName为后缀的类,可以在build的generated包下找到。

      arouter-api:xxx的这个jar包下的routes包中有三个已经存在的类:

      ARouter$$Group$$arouter:

      public class ARouter$$Group$$arouter implements IRouteGroup {
          public ARouter$$Group$$arouter() {
          }
      
          public void loadInto(Map<String, RouteMeta> atlas) {
              atlas.put("/arouter/service/autowired", RouteMeta.build(RouteType.PROVIDER, AutowiredServiceImpl.class, "/arouter/service/autowired", "arouter", (Map)null, -1, -2147483648));
              atlas.put("/arouter/service/interceptor", RouteMeta.build(RouteType.PROVIDER, InterceptorServiceImpl.class, "/arouter/service/interceptor", "arouter", (Map)null, -1, -2147483648));
          }
      }
      

      ARouter$$Providers$$arouterapi:

      public class ARouter$$Providers$$arouterapi implements IProviderGroup {
          public ARouter$$Providers$$arouterapi() {
          }
      
          public void loadInto(Map<String, RouteMeta> providers) {
              providers.put("com.alibaba.android.arouter.facade.service.AutowiredService", RouteMeta.build(RouteType.PROVIDER, AutowiredServiceImpl.class, "/arouter/service/autowired", "arouter", (Map)null, -1, -2147483648));
              providers.put("com.alibaba.android.arouter.facade.service.InterceptorService", RouteMeta.build(RouteType.PROVIDER, InterceptorServiceImpl.class, "/arouter/service/interceptor", "arouter", (Map)null, -1, -2147483648));
          }
      }
      

      ARouter$$Root$$arouterapi:

      public class ARouter$$Root$$arouterapi implements IRouteRoot {
          public ARouter$$Root$$arouterapi() {
          }
      
          public void loadInto(Map<String, Class<extends IRouteGroup>> routes) {
              routes.put("arouter", arouter.class);
          }
      }
      

      在build中还会自动生成ARouter$$Providers$$moduleName、ARouter$$Root$$moduleName,moduleName是模块的名字。以module是app为例,ARouter$$Root$$app为:

      public class ARouter$$Root$$app implements IRouteRoot {
        @Override
        public void loadInto(Map<String, Class<extends IRouteGroup>> routes) {
          routes.put("john", ARouter$$Group$$john.class);
          routes.put("rxseriers", ARouter$$Group$$rxseriers.class);
        }
      }
      

      前面分析的初始化中会调用到loadInto方法,这里就会把ARouter$$Group$$john.class和ARouter$$Group$$rxseriers.class保存到Warehouse.groupsIndex中。

      而Route注解生成的类的类名格式为ARouter$$Group$$groupName,知道了这些之后,我们可以得出结论,路由中的groupName不能是“arouter”,倘若相同,自动生成的class名就会和routes包下的默认存在的class同名,那么routes.put("arouter", ARouter$$Group$$john.class)传入的class就会被routes中的同名class替代,从而导致找不到自定义的路由类的问题。

    • 跳转

      Example:

      ARouter.getInstance().build(Const.LOGIN_ACTIVITY_PATH).withString("name","John").navigation()
      

      通过单例模式获取ARouter实例:

      public static ARouter getInstance() {
          if (!hasInit) {
              throw new InitException("ARouter::Init::Invoke init(context) first!");
          } else {
              if (instance == null) {
                  synchronized (ARouter.class) {
                      if (instance == null) {
                          instance = new ARouter();
                      }
                  }
              }
              return instance;
          }
      }
      

      可以看到实例化之前必须先初始化。

      build有多个重载方法:

      /**
       * Build postcard by path and default group
       */
      protected Postcard build(String path) {
          if (TextUtils.isEmpty(path)) {
              throw new HandlerException(Consts.TAG + "Parameter is invalid!");
          } else {
              PathReplaceService pService = ARouter.getInstance().navigation(PathReplaceService.class);
              if (null != pService) {
                  path = pService.forString(path);
              }
              return build(path, extractGroup(path), true);
          }
      }
      
      /**
       * Build postcard by uri
       */
      protected Postcard build(Uri uri) {
          if (null == uri || TextUtils.isEmpty(uri.toString())) {
              throw new HandlerException(Consts.TAG + "Parameter invalid!");
          } else {
              PathReplaceService pService = ARouter.getInstance().navigation(PathReplaceService.class);
              if (null != pService) {
                  uri = pService.forUri(uri);
              }
              return new Postcard(uri.getPath(), extractGroup(uri.getPath()), uri, null);
          }
      }
      
      /**
       * Build postcard by path and group
       */
      protected Postcard build(String path, String group, Boolean afterReplace) {
          if (TextUtils.isEmpty(path) || TextUtils.isEmpty(group)) {
              throw new HandlerException(Consts.TAG + "Parameter is invalid!");
          } else {
              if (!afterReplace) {
                  PathReplaceService pService = ARouter.getInstance().navigation(PathReplaceService.class);
                  if (null != pService) {
                      path = pService.forString(path);
                  }
              }
              return new Postcard(path, group);
          }
      }
      
      }
      

      其实就是两种方式,一个参数是String类型的path,这就是Route注解的path属性的值,另一个参数是Uri类型,这种形式最终使用的是Uri的path值,无论哪种形式,都必须是/groupName/的格式,extractGroup方法决定了这样:

      /**
       * Extract the default group from path.
       */
      private String extractGroup(String path) {
          if (TextUtils.isEmpty(path) || !path.startsWith("/")) {
              throw new HandlerException(Consts.TAG + "Extract the default group failed, the path must be start with '/' and contain more than 2 '/'!");
          }
      
          try {
              String defaultGroup = path.substring(1, path.indexOf("/", 1));
              if (TextUtils.isEmpty(defaultGroup)) {
                  throw new HandlerException(Consts.TAG + "Extract the default group failed! There's nothing between 2 '/'!");
              } else {
                  return defaultGroup;
              }
          } catch (Exception e) {
              logger.warning(Consts.TAG, "Failed to extract default group! " + e.getMessage());
              return null;
      }
      

      所以build之后得到了一个Postcard(明信片)对象,接下来是调用它的navigation方法。

      navigation方法同样有多个重载方法,所有可传参数有:Activity mContext, int requestCode, NavigationCallback callback。mContext若不传就是构建时传入的application上下文,requestCode是用以forResult方式打开Activity的需求,可以传一个callback以自定义处理一些特殊情况。

      最终会调用到_ARouter的navigation方法中:

      protected Object navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
          PretreatmentService pretreatmentService = ARouter.getInstance().navigation(PretreatmentService.class);
          if (null != pretreatmentService && !pretreatmentService.onPretreatment(context, postcard)) {
              // Pretreatment failed, navigation canceled.
              return null;
          }
      
          try {
              LogisticsCenter.completion(postcard);
          } catch (NoRouteFoundException ex) {
              logger.warning(Consts.TAG, ex.getMessage());
      
              if (debuggable()) {
                  // Show friendly tips for user.
                  runInMainThread(new Runnable() {
                      @Override
                      public void run() {
                          Toast.makeText(mContext, "There's no route matched!\n" +
                                  " Path = [" + postcard.getPath() + "]\n" +
                                  " Group = [" + postcard.getGroup() + "]", Toast.LENGTH_LONG).show();
                      }
                  });
              }
      
              if (null != callback) {
                  callback.onLost(postcard);
              } else {
                  // No callback for this invoke, then we use the global degrade service.
                  DegradeService degradeService = ARouter.getInstance().navigation(DegradeService.class);
                  if (null != degradeService) {
                      degradeService.onLost(context, postcard);
                  }
              }
      
              return null;
          }
      
          if (null != callback) {
              callback.onFound(postcard);
          }
      
          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;
      }
      

      这里只看核心代码,LogisticsCenter.completion(postcard)用于完善和检查Postcard的必要信息,暂时先不看,先看方法最终的逻辑:

      private Object _navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
          final Context currentContext = null == context mContext : context;
      
          switch (postcard.getType()) {
              case ACTIVITY:
                  // Build intent
                  final Intent intent = new Intent(currentContext, postcard.getDestination());
                  intent.putExtras(postcard.getExtras());
      
                  // Set flags.
                  int flags = postcard.getFlags();
                  if (-1 != flags) {
                      intent.setFlags(flags);
                  } else if (!(currentContext instanceof Activity)) {    // Non activity, need less one flag.
                      intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                  }
      
                  // Set Actions
                  String action = postcard.getAction();
                  if (!TextUtils.isEmpty(action)) {
                      intent.setAction(action);
                  }
      
                  // Navigation in main looper.
                  runInMainThread(new Runnable() {
                      @Override
                      public void run() {
                          startActivity(requestCode, currentContext, intent, postcard, callback);
                      }
                  });
      
                  break;
              case PROVIDER:
                  return postcard.getProvider();
              case BOARDCAST:
              case CONTENT_PROVIDER:
              case FRAGMENT:
                  Class fragmentMeta = postcard.getDestination();
                  try {
                      Object instance = fragmentMeta.getConstructor().newInstance();
                      if (instance instanceof Fragment) {
                          ((Fragment) instance).setArguments(postcard.getExtras());
                      } else if (instance instanceof android.support.v4.app.Fragment) {
                          ((android.support.v4.app.Fragment) instance).setArguments(postcard.getExtras());
                      }
      
                      return instance;
                  } catch (Exception ex) {
                      logger.error(Consts.TAG, "Fetch fragment instance error, " + TextUtils.formatStackTrace(ex.getStackTrace()));
                  }
              case METHOD:
              case SERVICE:
              default:
                  return null;
          }
      
          return null;
      }
      
      /**
       * Start activity
       *
       * @see ActivityCompat
       */
      private void startActivity(int requestCode, Context currentContext, Intent intent, Postcard postcard, NavigationCallback callback) {
          if (requestCode >= 0) {  // Need start for result
              if (currentContext instanceof Activity) {
                  ActivityCompat.startActivityForResult((Activity) currentContext, intent, requestCode, postcard.getOptionsBundle());
              } else {
                  logger.warning(Consts.TAG, "Must use [navigation(activity, ...)] to support [startActivityForResult]");
              }
          } else {
              ActivityCompat.startActivity(currentContext, intent, postcard.getOptionsBundle());
          }
      
          if ((-1 != postcard.getEnterAnim() && -1 != postcard.getExitAnim()) && currentContext instanceof Activity) {    // Old version.
              ((Activity) currentContext).overridePendingTransition(postcard.getEnterAnim(), postcard.getExitAnim());
          }
      
          if (null != callback) { // Navigation over.
              callback.onArrival(postcard);
          }
      }
      

      可以看到,最底层也是调用了startActvity或startActivityForResult(requestCode大于0且context是Activity)来处理回调,动画和NavigationCallback的处理也是在这里。

      回过头来我们看一下LogisticsCenter.completion(postcard)方法,它只在navigation方法里面最开始调用:

      public synchronized static void completion(Postcard postcard) {
          if (null == postcard) {
              throw new NoRouteFoundException(TAG + "No postcard!");
          }
      
          RouteMeta routeMeta = Warehouse.routes.get(postcard.getPath());
          if (null == routeMeta) {    // Maybe its does't exist, or didn't load.
              Class<extends IRouteGroup> groupMeta = Warehouse.groupsIndex.get(postcard.getGroup());  // Load route meta.
              if (null == groupMeta) {
                  throw new NoRouteFoundException(TAG + "There is no route match the path [" + postcard.getPath() + "], in group [" + postcard.getGroup() + "]");
              } else {
                  // Load route and cache it into memory, then delete from metas.
                  try {
                      if (ARouter.debuggable()) {
                          logger.debug(TAG, String.format(Locale.getDefault(), "The group [%s] starts loading, trigger by [%s]", postcard.getGroup(), postcard.getPath()));
                      }
      
                      IRouteGroup iGroupInstance = groupMeta.getConstructor().newInstance();
                      iGroupInstance.loadInto(Warehouse.routes);
                      Warehouse.groupsIndex.remove(postcard.getGroup());
      
                      if (ARouter.debuggable()) {
                          logger.debug(TAG, String.format(Locale.getDefault(), "The group [%s] has already been loaded, trigger by [%s]", postcard.getGroup(), postcard.getPath()));
                      }
                  } catch (Exception e) {
                      throw new HandlerException(TAG + "Fatal exception when loading group meta. [" + e.getMessage() + "]");
                  }
      
                  completion(postcard);   // Reload
              }
          } else {
              postcard.setDestination(routeMeta.getDestination());
              postcard.setType(routeMeta.getType());
              postcard.setPriority(routeMeta.getPriority());
              postcard.setExtra(routeMeta.getExtra());
      
              Uri rawUri = postcard.getUri();
              if (null != rawUri) {   // Try to set params into bundle.
                  Map<String, String> resultMap = TextUtils.splitQueryParameters(rawUri);
                  Map<String, Integer> paramsType = routeMeta.getParamsType();
      
                  if (MapUtils.isNotEmpty(paramsType)) {
                      // Set value by its type, just for params which annotation by @Param
                      for (Map.Entry<String, Integer> params : paramsType.entrySet()) {
                          setValue(postcard,
                                  params.getValue(),
                                  params.getKey(),
                                  resultMap.get(params.getKey()));
                      }
      
                      // Save params name which need auto inject.
                      postcard.getExtras().putStringArray(ARouter.AUTO_INJECT, paramsType.keySet().toArray(new String[]{}));
                  }
      
                  // Save raw uri
                  postcard.withString(ARouter.RAW_URI, rawUri.toString());
              }
      
              switch (routeMeta.getType()) {
                  case PROVIDER:  // if the route is provider, should find its instance
                      // Its provider, so it must implement IProvider
                      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的时候已经把自动生成的Root类保存进了Warehouse.groupsIndex了吧,这里首先会从Warehouse.groupsIndex中取RouteMeta,如果没取到说明之前没有跳转过,则从Warehouse.groupsIndex中取得之前存入的class,取到之后调用groupMeta.getConstructor().newInstance()反射生成实例,调用它的loadInto(Warehouse.routes)方法,这里我定义的是@Route(path="/john/"),所以生成的是ARouter$$Group$$john:

      /**
       * DO NOT EDIT THIS FILE!!! IT WAS GENERATED BY AROUTER. */
      public class ARouter$$Group$$john implements IRouteGroup {
        @Override
        public void loadInto(Map<String, RouteMeta> atlas) {
          atlas.put("/john/", RouteMeta.build(RouteType.ACTIVITY, LoginActivity.class, "/john/", "john", new java.util.HashMap<String, Integer>(){{put("name", 8); put("age", 3); }}, -1, -2147483648));
        }
      }
      

      那么loadInto方法就是这里的,做的工作就是把生成的RouteMeta对象保存进Warehouse.routes,后面再次递归调用completion方法,再次进来,Warehouse.routes内就含有了RouteMeta对象。

      我们看一下自动生成的RouteMeta都含有什么关键信息。

      首先第一个参数RouteType,标志跳转目标的类型:

      public enum RouteType {
          ACTIVITY(0, "android.app.Activity"),
          SERVICE(1, "android.app.Service"),
          PROVIDER(2, "com.alibaba.android.arouter.facade.template.IProvider"),
          CONTENT_PROVIDER(-1, "android.app.ContentProvider"),
          BOARDCAST(-1, ""),
          METHOD(-1, ""),
          FRAGMENT(-1, "android.app.Fragment"),
          UNKNOWN(-1, "Unknown route type");
      
          int id;
          String className;
      
          public int getId() {
              return id;
          }
      
          public RouteType setId(int id) {
              this.id = id;
              return this;
          }
      
          public String getClassName() {
              return className;
          }
      
          public RouteType setClassName(String className) {
              this.className = className;
              return this;
          }
      
          RouteType(int id, String className) {
              this.id = id;
              this.className = className;
          }
      
          public static RouteType parse(String name) {
              for (RouteType routeType : RouteType.values()) {
                  if (routeType.getClassName().equals(name)) {
                      return routeType;
                  }
              }
      
              return UNKNOWN;
          }
      }
      

      第二个参数是Class类型,是跳转目标类。第三个参数是Route定义的path。第四个参数是group。第五个参数是Map,用于保存Autowired参数类型的,value是整型,表示enum.ordinal:

      public enum TypeKind {
          // Base type
          BOOLEAN,
          BYTE,
          SHORT,
          INT,
          LONG,
          CHAR,
          FLOAT,
          DOUBLE,
      
          // Other type
          STRING,
          SERIALIZABLE,
          PARCELABLE,
          OBJECT;
      }
      

      通过setValue方法赋值到postcard的mBundle:

      private static void setValue(Postcard postcard, Integer typeDef, String key, String value) {
          if (TextUtils.isEmpty(key) || TextUtils.isEmpty(value)) {
              return;
          }
      
          try {
              if (null != typeDef) {
                  if (typeDef == TypeKind.BOOLEAN.ordinal()) {
                      postcard.withBoolean(key, Boolean.parseBoolean(value));
                  } else if (typeDef == TypeKind.BYTE.ordinal()) {
                      postcard.withByte(key, Byte.parseByte(value));
                  } else if (typeDef == TypeKind.SHORT.ordinal()) {
                      postcard.withShort(key, Short.parseShort(value));
                  } else if (typeDef == TypeKind.INT.ordinal()) {
                      postcard.withInt(key, Integer.parseInt(value));
                  } else if (typeDef == TypeKind.LONG.ordinal()) {
                      postcard.withLong(key, Long.parseLong(value));
                  } else if (typeDef == TypeKind.FLOAT.ordinal()) {
                      postcard.withFloat(key, Float.parseFloat(value));
                  } else if (typeDef == TypeKind.DOUBLE.ordinal()) {
                      postcard.withDouble(key, Double.parseDouble(value));
                  } else if (typeDef == TypeKind.STRING.ordinal()) {
                      postcard.withString(key, value);
                  } else if (typeDef == TypeKind.PARCELABLE.ordinal()) {
                      // TODO : How to description parcelable value with string?
                  } else if (typeDef == TypeKind.OBJECT.ordinal()) {
                      postcard.withString(key, value);
                  } else {    // Compatible compiler sdk 1.0.3, in that version, the string type = 18
                      postcard.withString(key, value);
                  }
              } else {
                  postcard.withString(key, value);
              }
          } catch (Throwable ex) {
              logger.warning(Consts.TAG, "LogisticsCenter setValue failed! " + ex.getMessage());
          }
      }
      

      注意这里setValue使用resultMap.get(params.getKey())来尝试获取Uri中的值,就是Uri中‘?’到‘#’(可以没有)之间的‘xx=xx&xx=xx&...’键值对。

      第六个参数是priority,设置优先级,通过Route注解的priority属性指定,最后一个参数通过Route注解的extras属性指定。

    • 参数传递

      对于基本的类型传递,Postcard中都有对应的方法withXxx()来设置,对于实例化类型,如果实现了Serializable或Parcelable的,可以调用withSerializable和withParcelable方法设置。

      而对于自定义的未实现上述接口的则需要调用withObject方法:

      public Postcard withObject(@Nullable String key, @Nullable Object value) {
          serializationService = ARouter.getInstance().navigation(SerializationService.class);
          mBundle.putString(key, serializationService.object2Json(value));
          return this;
      }
      

      可见需要定义实现自SerializationService的类,譬如:

      @Route(path = AppConstant.ARouteService.SERVICE_SERIALIZATION)
      public class ModuleSerializationRouteServiceImpl implements SerializationService {
      
          private Gson gson;
          private GsonBuilder gsonBuilder;
      
          @Override
          public <T> T json2Object(String input, Class<T> clazz) {
              return null;
          }
      
          @Override
          public String object2Json(Object instance) {
              return gson.toJson(instance);
          }
      
          @Override
          public <T> T parseObject(String input, Type clazz) {
              return gson.fromJson(input, clazz);
          }
      
          @Override
          public void init(Context context) {
              gsonBuilder = new GsonBuilder();
              gsonBuilder.registerTypeAdapter(Class.class, new ClassTypeAdapter());
              gson = gsonBuilder.create();
          }
      }
      

      这里的navigation(Class<extends T> service)是:

      protected <T> T navigation(Class<extends T> service) {
          try {
              Postcard postcard = LogisticsCenter.buildProvider(service.getName());
      
              // Compatible 1.0.5 compiler sdk.
              // Earlier versions did not use the fully qualified name to get the service
              if (null == postcard) {
                  // No service, or this service in old version.
                  postcard = LogisticsCenter.buildProvider(service.getSimpleName());
              }
      
              if (null == postcard) {
                  return null;
              }
      
              LogisticsCenter.completion(postcard);
              return (T) postcard.getProvider();
          } catch (NoRouteFoundException ex) {
              logger.warning(Consts.TAG, ex.getMessage());
              return null;
          }
      }
      

      LogisticsCenter.buildProvider从Warehouse.providersIndex中取得provider:

      public static Postcard buildProvider(String serviceName) {
          RouteMeta meta = Warehouse.providersIndex.get(serviceName);
      
          if (null == meta) {
              return null;
          } else {
              return new Postcard(meta.getPath(), meta.getGroup());
          }
      }
      

      实例化SerializationService和调用它的init的代码就在completion中的:

      switch (routeMeta.getType()) {
          case PROVIDER:  // if the route is provider, should find its instance
              // Its provider, so it must implement IProvider
              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;
                  ...
                
      }
      

      所以实际上withObject存储的是对象的json字符串。

    • 传参自动注入

      传参自然需要获取参数,上面我们知道最终也是通过intent和bundle传递的参数,那么获取的时候当然也可是通过getIntent().getExtras()方法获取参数值。

      对于ARouter,这些工作我们还可以通过Autowiredz注解自动完成。

      要完成自动注入需要两点,一点是必须在每个需要自动注入的属性上面加Autowired注解,它有三个注解属性,必须要有的是name属性,这个属性的值必须和跳转时传递到bundle的key相同,如果指定了name属性,则字段的名字不必和bundle的key相同,反之则必须相同;第二个是required属性,如果设置为true则表示该Field的值不能为null,否则会crash;最后一个参数是desc,用于描述Field的作用。注意如果是kotlin类,则需要同时加上@JvmField属性,否则不能通过kapt编译。

      另一点是需要在使用自动注入的字段的值之前调用ARouter.getInstance().inject(this)方法,否则使用值为null的实例会抛出NullPointerException异常。

      下面我们看看,对于这些要求ARouter是怎么通过代码设置的。

      首先,含有Autowired注解的类会被自动生成一个名为“类名$$ARouter$$Autowired”的类,那么ARouter.getInstance().inject(this)方法调用了_ARouter的inject方法:

      static void inject(Object thiz) {
          AutowiredService autowiredService = ((AutowiredService) ARouter.getInstance().build("/arouter/service/autowired").navigation());
          if (null != autowiredService) {
              autowiredService.autowire(thiz);
          }
      }
      

      可见,这里会找到path为/arouter/service/autowired的AutowiredService,那这个接口的实现类在哪里,还记得我们初始化的时候for循环loadInto操作吗,就在那个时候前缀为com.alibaba.android.arouter.routes.ARouter$$Providers的类被存到了Warehouse.providesIndex中,所以jar包arouter-api中的ARouter$$Providers$$arouterapi类自然也被加进去了,所以completion中找到这个类调用它的loadInto方法:

      public void loadInto(Map<String, RouteMeta> providers) {
          providers.put("com.alibaba.android.arouter.facade.service.AutowiredService", RouteMeta.build(RouteType.PROVIDER, AutowiredServiceImpl.class, "/arouter/service/autowired", "arouter", (Map)null, -1, -2147483648));
          providers.put("com.alibaba.android.arouter.facade.service.InterceptorService", RouteMeta.build(RouteType.PROVIDER, InterceptorServiceImpl.class, "/arouter/service/interceptor", "arouter", (Map)null, -1, -2147483648));
      }
      

      所以path为/arouter/service/autowired的就是AutowiredServiceImpl类,接下来调用它的autowire方法,传入的是被跳转页Activity的实例:

      @Override
      public void autowire(Object instance) {
          doInject(instance, null);
      }
      
      /**
       * Recursive injection
       *
       * @param instance who call me.
       * @param parent   parent of me.
       */
      private void doInject(Object instance, Class<?> parent) {
          Class<?> clazz = null == parent instance.getClass() : parent;
      
          ISyringe syringe = getSyringe(clazz);
          if (null != syringe) {
              syringe.inject(instance);
          }
      
          //这里可见会自动把其父类中的Autowired字段注入
          Class<?> superClazz = clazz.getSuperclass();
          // has parent and its not the class of framework.
          if (null != superClazz && !superClazz.getName().startsWith("android")) {
              doInject(instance, superClazz);
          }
      }
      

      来看看getSyringe方法:

      private ISyringe getSyringe(Class<?> clazz) {
          String className = clazz.getName();
      
          try {
              if (!blackList.contains(className)) {
                  ISyringe syringeHelper = classCache.get(className);
                  if (null == syringeHelper) {  // No cache.
                      syringeHelper = (ISyringe) Class.forName(clazz.getName() + SUFFIX_AUTOWIRED).getConstructor().newInstance();
                  }
                  classCache.put(className, syringeHelper);
                  return syringeHelper;
              }
          } catch (Exception e) {
              blackList.add(className);    // This instance need not autowired.
          }
      
          return null;
      }
      

      (ISyringe) Class.forName(clazz.getName() + SUFFIX_AUTOWIRED).getConstructor().newInstance()这行代码就是获取自动生成的名为“类名$$ARouter$$Autowired”的类,这里比如说是LoginActivity类,那么这行代码就会找到LoginActivity$$ARouter$$Autowired:

      public class LoginActivity$$ARouter$$Autowired implements ISyringe {
        private SerializationService serializationService;
      
        @Override
        public void inject(Object target) {
          serializationService = ARouter.getInstance().navigation(SerializationService.class);
          LoginActivity substitute = (LoginActivity)target;
          substitute.name = substitute.getIntent().getExtras() == null substitute.name : substitute.getIntent().getExtras().getString("name", substitute.name);
          substitute.howOld = substitute.getIntent().getIntExtra("age", substitute.howOld);
        }
      }
      

      可见这个类实现自ISyringe接口,所以调用ISyringe的inject方法就是调用LoginActivity$$ARouter$$Autowired的inject方法,可以看到,这个方法里给这个Activity实例的相关字段赋值,取值来源也是通过getIntent()。

    • Service Management

      // Declaration interface, other components get the service instance through the interface
      public interface HelloService extends IProvider {
          String sayHello(String name);
      }
      
      @Route(path = "/yourservicegroupname/hello", name = "test service")
      public class HelloServiceImpl implements HelloService {
      
          @Override
          public String sayHello(String name) {
              return "hello, " + name;
          }
      
          @Override
          public void init(Context context) {
      
          }
      }
      

      结合Autowired就可以实现自动注入,原理同上。

    • NavigationCallback

      如果你需要在路由正常跳转或者没找到时做一些回调处理,那么可以在调用navigation方法时传入一个NavigationCallback对象用于处理:

      public interface NavigationCallback {
      
          /**
           * Callback when find the destination.
           *
           * @param postcard meta
           */
          void onFound(Postcard postcard);
      
          /**
           * Callback after lose your way.
           *
           * @param postcard meta
           */
          void onLost(Postcard postcard);
      
          /**
           * Callback after navigation.
           *
           * @param postcard meta
           */
          void onArrival(Postcard postcard);
      
          /**
           * Callback on interrupt.
           *
           * @param postcard meta
           */
          void onInterrupt(Postcard postcard);
      }
      
    • DegradeService

      对于NoRouteFoundException发生时,你可以通过NavigationCallback来处理,但需要每次navigation都需要指定,如果你需要指定一个全局通用的onLost处理,你可以定义一个类实现DegradeService接口,重写onLost方法,这样所有的navigation发生NoRouteFoundException时都会进入到此方法,因为navigation方法中:

      try {
          LogisticsCenter.completion(postcard);
      } catch (NoRouteFoundException ex) {
          logger.warning(Consts.TAG, ex.getMessage());
      
          if (debuggable()) {
              // Show friendly tips for user.
              runInMainThread(new Runnable() {
                  @Override
                  public void run() {
                      Toast.makeText(mContext, "There's no route matched!\n" +
                              " Path = [" + postcard.getPath() + "]\n" +
                              " Group = [" + postcard.getGroup() + "]", Toast.LENGTH_LONG).show();
                  }
              });
          }
      
          if (null != callback) {
              callback.onLost(postcard);
          } else {
              // No callback for this invoke, then we use the global degrade service.
              DegradeService degradeService = ARouter.getInstance().navigation(DegradeService.class);
              if (null != degradeService) {
                  degradeService.onLost(context, postcard);
              }
          }
      
          return null;
      }
      

      可见,优先走NavigationCallback的onLost。

    • Interceptor

      navigation方法中,判断postcard.isGreenChannel()是否为false来决定是否需要拦截处理,如果是false,则进行拦截处理:

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

      interceptorService是什么时候实例化的呢?答案还是在初始化里面,init方法里,若成功执行完 _ARouter.init(application)方法则会返回true赋值给hasInit,hasInit为true会执行 _ARouter.afterInit()方法:

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

      同AutowiredService一样,初始化时在ARouter$$Providers$$arouterapi的loadInto中path为/arouter/service/interceptor的InterceptorServiceImpl.class被存进了Warehouse.interceptorsIndex,所以这里afterInit会取到InterceptorServiceImpl.class,它的doInterceptions方法如下:

      @Override
      public void doInterceptions(final Postcard postcard, final InterceptorCallback callback) {
          if (null != Warehouse.interceptors && Warehouse.interceptors.size() > 0) {
      
              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);
                          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);
          }
      }
      

      checkInterceptorsInitStatus():

      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,则会一直阻塞当前线程等待,每10秒重新检查一次,那interceptorHasInit在哪里改变的?还记得在completion里:

      switch (routeMeta.getType()) {
          case PROVIDER:  // if the route is provider, should find its instance
              // Its provider, so it must implement IProvider
              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;
          ...
      }
      

      此时是InterceptorServiceImpl,为什么InterceptorServiceImpl的Type是PROVIDER而不是INTERCEPTOR呢?因为ARouter$$Providers$$arouterapi的loadInto存入时设置的就是PROVIDER:

      providers.put("com.alibaba.android.arouter.facade.service.InterceptorService", RouteMeta.build(RouteType.PROVIDER, InterceptorServiceImpl.class, "/arouter/service/interceptor", "arouter", (Map)null, -1, -2147483648));
      

      所以provider.init()方法就是InterceptorServiceImpl的init方法:

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

      可以看到,这里会把Warehouse.interceptorsIndex里的所以interceptor都执行init方法,然后设置interceptorHasInit为true,因为这里的init方法是新建线程执行的,所以在执行的同时,completion后面的doInterceptions方法也在异步执行,所以现在可以理解checkInterceptorsInitStatus的意义了,它就是在等init完成,调用interceptorInitLock.notifyAll()结束wait,从而继续往下执行doInterceptions方法,如果因为等待时间太长被系统终止则会抛出InterruptedException异常。

      doInterceptions下面的代码就是开启子线程执行_execute方法,interceptorCounter.await同步等待 _executes执行完:

        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() {
                      @Override
                      public 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.
                      }
      
                      @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();
                          // 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());
      //                    }
                      }
                  });
              }
          }
      

      这里就是对所有的Interceptor依次执行process方法,假如定义了一个Interceptor:

      @Interceptor(priority = 8, name = "test interceptor")
      public class TestInterceptor implements IInterceptor {
          @Override
          public void process(Postcard postcard, InterceptorCallback callback) {
              ...
              // No problem! hand over control to the framework
              if(LoginUtils.isLogin()){
                      callback.onContinue(postcard);
              }else{
                callback.onInterrupt(new Throwable("The Route needs login first!"));
              }
              
              // Interrupt routing process
              // callback.onInterrupt(new RuntimeException("Something exception"));      
      
              // The above two types need to call at least one of them, otherwise it will not continue routing
          }
      
          @Override
          public void init(Context context) {
              // Interceptor initialization, this method will be called when sdk is initialized, it will only be called once
          }
      }
      

      这里模拟了跳转需要登录的逻辑,如果已登录则执行跳转,如果未登录则intertupt。在process中调用callback.onContinue或者callback.onInterrupt方法就会回到_execute中处理。

      执行完后判断如果interceptorCounter的getCount大于0说明因Postcard超时时间到期中止,这个超时时间可以设置,默认300秒,如果postcard.getTag()不为null说明有异常产生,若顺利的话会走到callback.onContinue(postcard),就会回调到_ARouter.navigation方法里doIntercaptions传入的匿名InterceptorCallback对象的onContinue方法,从而执行 _navigation方法执行跳转。

      至此,拦截工作就完成了。

    • Priority

      Interceptor的priority在哪设置的?@Interceptor注解修饰的类会被自动创建的ARouter$$Interceptors$$app存进Warehouse.interceptorsIndex中,原理同上:

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

      因为我创建的Interceptor是:

      @Interceptor(name = "LoginInInterceptor" , priority = 5)
      class LoginInterceptor : IInterceptor {
      

      所以interceptors.put的参数分别是5和LoginInterceptor.class,5是设置的priority,为什么把priority作为key呢?因为Warehouse的interceptorsIndex是一个UniqueKeyTreeMap,它的put方法:

      @Override
      public V put(K key, V value) {
          if (containsKey(key)) {
              throw new RuntimeException(String.format(tipText, key));
          } else {
              return super.put(key, value);
          }
      }
      

      比其父类多的一个操作是首先判断不能添加重复key值的value。然后调用了父类TreeMap的put方法:

      public V put(K key, V value) {
          TreeMapEntry<K,V> t = root;
          if (t == null) {
              compare(key, key); // type (and possibly null) check
      
              root = new TreeMapEntry<>(key, value, null);
              size = 1;
              modCount++;
              return null;
          }
          int cmp;
          TreeMapEntry<K,V> parent;
          // split comparator and comparable paths
          Comparator<super K> cpr = comparator;
          if (cpr != null) {
              do {
                  parent = t;
                  cmp = cpr.compare(key, t.key);
                  if (cmp < 0)
                      t = t.left;
                  else if (cmp > 0)
                      t = t.right;
                  else
                      return t.setValue(value);
              } while (t != null);
          }
          else {
              if (key == null)
                  throw new NullPointerException();
              @SuppressWarnings("unchecked")
                  Comparable<super K> k = (Comparable<super K>) key;
              do {
                  parent = t;
                  cmp = k.compareTo(t.key);
                  if (cmp < 0)
                      t = t.left;
                  else if (cmp > 0)
                      t = t.right;
                  else
                      return t.setValue(value);
              } while (t != null);
          }
          TreeMapEntry<K,V> e = new TreeMapEntry<>(key, value, parent);
          if (cmp < 0)
              parent.left = e;
          else
              parent.right = e;
          fixAfterInsertion(e);
          size++;
          modCount++;
          return null;
      }
      

      可见会按照key值将新元素插入到合适的位置,顺序就是key值越小越靠前,而我们前面看到执行process的时候都是从Warehouse.interceptorsIndex的第0个元素开始,这也就是为什么它文档上写的priority越小优先级越高。

      Route注解里面也可以设置priority,但是我发现它好像没有用到,我唯一能想到的就是可以在NavigationCallback或者Interceptor中通过Postcard对象取到它然后根据它的值来做点什么逻辑,同样extra属性也是这样。

    • Pretreatment Service

      在navigation方法的最前面有这样几行代码:

      PretreatmentService pretreatmentService = ARouter.getInstance().navigation(PretreatmentService.class);
      if (null != pretreatmentService && !pretreatmentService.onPretreatment(context, postcard)) {
          // Pretreatment failed, navigation canceled.
          return null;
      }
      

      如果说Interceptor是在找到路由后、跳转之前的处理拦截,那PretreatmentService就是在查找路由之前的处理拦截:

      @Route(path = "/xxx/xxx")
      public class PretreatmentServiceImpl implements PretreatmentService {
          @Override
          public boolean onPretreatment(Context context, Postcard postcard) {
              // Do something before the navigation, if you need to handle the navigation yourself, the method returns false
          }
      
          @Override
          public void init(Context context) {
      
          }
      }
      

      官方注释告诉我们,如果你不希望通过ARouter自动跳转的话这里可以返回false,就不会执行查找路由等操作了。

      注意,PretreatmentService只能定义一个,因为:

      public class ARouter$$Providers$$app implements IProviderGroup {
        @Override
        public void loadInto(Map<String, RouteMeta> providers) {
          providers.put("com.alibaba.android.arouter.facade.service.PretreatmentService", RouteMeta.build(RouteType.PROVIDER, MyPreTreatmentService.class, "/lllSS/myPretreatmentService", "lllSS", null, 1, -2147483648));
          providers.put("com.alibaba.android.arouter.facade.service.PretreatmentService", RouteMeta.build(RouteType.PROVIDER, OtherPretreatmentService.class, "/lllSS/otherPretreatmentService", "lllSS", null, 2, -2147483648));
        }
      }
      

      定义多个,key值是一样的,因此只有最后一个会生效。

    • Rewrite URL

      在 _ARouter.build方法里有这样的处理:

      PathReplaceService pService = ARouter.getInstance().navigation(PathReplaceService.class);
      if (null != pService) {
          //path是String
          path = pService.forString(path);
          //path是Uri
          //path = pService.forUri(path);
      }
      

      也就是说出build传入的路由地址还可以通过PathReplaceService的api统一处理。

      定义如下:

      // Implement the PathReplaceService interface
      @Route(path = "/xxx/xxx")
      public class PathReplaceServiceImpl implements PathReplaceService {
          /**
          * For normal path.
          *
          * @param path raw path
          */
          String forString(String path) {
              // Custom logic
              return path;
          }
      
      /**
          * For uri type.
          *
          * @param uri raw uri
          */
          Uri forUri(Uri uri) {
              // Custom logic
              return url;
          }
      }
      

      同样,定义多个只会以ARouter$$Providers$$moduleName的loadInto里面put的最后一个key为com.alibaba.android.arouter.facade.service.PathReplaceService的为准。

  • 其他API description

    // Build a standard route request
    ARouter.getInstance().build("/home/main").navigation();
    
    // Build a standard route request, via URI
    Uri uri;
    ARouter.getInstance().build(uri).navigation();
    
    // Build a standard route request, startActivityForResult
    // The first parameter must be Activity and the second parameter is RequestCode
    ARouter.getInstance().build("/home/main", "ap").navigation(this, 5);
    
    // Pass Bundle directly
    Bundle params = new Bundle();
    ARouter.getInstance()
        .build("/home/main")
        .with(params)
        .navigation();
    
    // Set Flag
    ARouter.getInstance()
        .build("/home/main")
        .withFlags();
        .navigation();
    
    // For fragment
    Fragment fragment = (Fragment) ARouter.getInstance().build("/test/fragment").navigation();
                        
    // transfer the object 
    ARouter.getInstance()
        .withObject("key", new TestObj("Jack", "Rose"))
        .navigation();
    
    // Think the interface is not enough, you can directly set parameter into Bundle
    ARouter.getInstance()
            .build("/home/main")
            .getExtra();
    
    // Transition animation (regular mode)
    ARouter.getInstance()
        .build("/test/activity2")
        .withTransition(R.anim.slide_in_bottom, R.anim.slide_out_bottom)
        .navigation(this);
    
    // Transition animation (API16+)
    ActivityOptionsCompat compat = ActivityOptionsCompat.
        makeScaleUpAnimation(v, v.getWidth() / 2, v.getHeight() / 2, 0, 0);
    
    // ps. makeSceneTransitionAnimation, When using shared elements, you need to pass in the current Activity in the navigation method
    
    ARouter.getInstance()
        .build("/test/activity2")
        .withOptionsCompat(compat)
        .navigation();
            
    // Use green channel (skip all interceptors)
    ARouter.getInstance().build("/home/main").greenChannel().navigation();
    
    // Use your own log tool to print logs
    ARouter.setLogger();
    
    // Use your custom thread pool
    ARouter.setExecutor();
    
  • 其他总结

    如果代码中使用了Route注解,build生成的类有两种情况,一种是非IProvider,会自动生成名为ARouter$$Root$$(ModuleName模块名)的class,比如Activity、Fragment这些类上的Route注解,这个class只会有一个,针对这种情况还会生成对应的名为ARouter$$Group$$(groupName路由最前面两个‘/’之间的string)的class,这个会有多个,ARouter$$Root$$(ModuleName模块名)的loadInto用于put这些class,实际就是Warehouse.groupsIndex里保存的就是这些class。譬如:

    public class ARouter$$Root$$app implements IRouteRoot {
      @Override
      public void loadInto(Map<String, Class<extends IRouteGroup>> routes) {
        routes.put("john", ARouter$$Group$$john.class);
        routes.put("lllSS", ARouter$$Group$$lllSS.class);
        routes.put("rxseriers", ARouter$$Group$$rxseriers.class);
      }
    }
    

    另一种是实现自IProvider的类,会自动生成名为ARouter$$Providers$$(ModuleName模块名)的class,Warehouse.providersIndex里保存的就是这些class。譬如:

    public class ARouter$$Providers$$app implements IProviderGroup {
      @Override
      public void loadInto(Map<String, RouteMeta> providers) {
        providers.put("com.alibaba.android.arouter.facade.service.PretreatmentService", RouteMeta.build(RouteType.PROVIDER, MyPreTreatmentService.class, "/lllSS/myPretreatmentService", "lllSS", null, 1, -2147483648));
        providers.put("com.mph.rxseriers.arouter.MyLogicService", RouteMeta.build(RouteType.PROVIDER, MyLogicServiceImpl.class, "/lllSS/my_logic_service", "lllSS", null, -1, -2147483648));
        providers.put("com.alibaba.android.arouter.facade.service.PathReplaceService", RouteMeta.build(RouteType.PROVIDER, MyPathReplaceService.class, "/lllSS/my_replace_path_service", "lllSS", null, -1, -2147483648));
        providers.put("com.alibaba.android.arouter.facade.service.PretreatmentService", RouteMeta.build(RouteType.PROVIDER, OtherPretreatmentService.class, "/lllSS/otherPretreatmentService", "lllSS", null, 2, -2147483648));
        providers.put("com.alibaba.android.arouter.facade.service.PathReplaceService", RouteMeta.build(RouteType.PROVIDER, OtherPathReplaceService.class, "/lllSS/other_replace_path_service", "lllSS", null, -1, -2147483648));
      }
    }
    

    如果代码中使用了Interceptor注解且实现自IInterceptor的类,则会自动生成名为ARouter$$Interceptors(ModuleName模块名)的class,Warehouse.interceptorsIndex里保存的就是这些class。譬如:

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

https://www.xamrdz.com/database/63f1994065.html

相关文章: