ARoute源码分析

271 阅读3分钟

ARoute

前言

最近公司有一个总路由的架构需求 ,这里看到了ARoute,路由的思想感觉很有意思 ,简单翻阅了源码总结下来

使用

 plugins { id 'kotlin-kapt' }
 ​
 android {
     ...
     
     kapt {
         arguments {
             arg("AROUTER_MODULE_NAME", project.getName())
         }
     }
 }
 ​
 implementation 'com.alibaba:arouter-api:1.5.2'
 kapt 'com.alibaba:arouter-compiler:1.5.2'

MainActivity

 //执行跳转逻辑
 ARouter.getInstance().build(ARouteTable.HOME)
     .withString("name", "小度小度")
     .navigation()

HomeActivity

 @Route(path = ARouteTable.HOME)
 class HomeActivity : ComponentActivity() {
     @Autowired
     @JvmField
     var name: String? = null
 ​
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
         ARouter.getInstance().inject(this)
     }
 }

原理分析

生成 (编译时)

 public class ARouter$$Root$$app implements IRouteRoot {
   @Override
   public void loadInto(Map<String, Class<? extends IRouteGroup>> routes) {
     //
     routes.put("app", ARouter$$Group$$app.class);
   }
 }
 public class ARouter$$Group$$app implements IRouteGroup {
   @Override
   public void loadInto(Map<String, RouteMeta> atlas) {
     //  /app/home 就是我们添加的path , RouteMeta会将我们的参数 跳转的Activity构建出来
     atlas.put("/app/home", RouteMeta.build(RouteType.ACTIVITY, HomeActivity.class, "/app/home", "app", new java.util.HashMap<String, Integer>(){{put("name", 8); }}, -1, -2147483648));
   }
 }
 public class ARouter$$Providers$$app implements IProviderGroup {
   @Override
   public void loadInto(Map<String, RouteMeta> providers) {
   }
 }

经过编译之后 Aroute 通过APT ,Javapont 动态生成了以上三个文件

注入 (运行时)

我们先看在Application中的init函数

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

我们接着查看LogisticsCenterinit

 public synchronized static void init(Context context, ThreadPoolExecutor tpe) throws HandlerException {
     mContext = context;
     executor = tpe;
 ​
     try {
         long startInit = System.currentTimeMillis();
         //load by plugin first
         loadRouterMap();
         //如果是通过plugin注册的,也就是动态生成了代码
         if (registerByPlugin) {
             logger.info(TAG, "Load router map by arouter-auto-register plugin.");
         } else {
             Set<String> routerMap;
             
             ...
                 
             //如果是新安装的apk 或者是debug模式
             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.
                     //根据包获取所有生成的Class文件
                     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) {
                 //如果是以 "com.alibaba.android.arouter.routes" + "ARouter" + "$$Root" 开头的 
                 //1 com.alibaba.android.arouter.routes.ARouter$$Root$$app 这是我们的全路径
                 //这里可以注意一下 ARoute自己也有一个文件满足这个条件 也会执行
                 if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_ROOT)) {
                     //反射调用函数 loadInto
                     ((IRouteRoot) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.groupsIndex);
                 } //如果是Interceptors
                 else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_INTERCEPTORS)) {
                     //反射调用loadInto
                     ((IInterceptorGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.interceptorsIndex);
                 }//如果是Providers 也是反射调用 
                 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() + "]");
     }
 }
  1. 这里其实我们这里就是执行了 routes.put("app", ARouter$$Group$$app.class);这段代码 ,将ARouter$$Group$$app.class 放入app的表中

我们再看一看是获取class文件的

 public static Set<String> getFileNameByPackageName(Context context, final String packageName) throws PackageManager.NameNotFoundException, IOException, InterruptedException {
     final Set<String> classNames = new HashSet<>();
     
     //根据上下文获取所有的资源路径
     List<String> paths = getSourcePaths(context);
     final CountDownLatch parserCtl = new CountDownLatch(paths.size());
 ​
     for (final String path : paths) {
         DefaultPoolExecutor.getInstance().execute(new Runnable() {
             @Override
             public void run() {
                 DexFile dexfile = null;
                 
                 try {
                     //如果是以.zip结尾的
                     if (path.endsWith(EXTRACTED_SUFFIX)) {
                         //NOT use new DexFile(path), because it will throw "permission error in /data/dalvik-cache"     
                         //解析成dexfile文件
                         dexfile = DexFile.loadDex(path, path + ".tmp", 0);
                     } else {
                         dexfile = new DexFile(path);
                     }
 ​
                     Enumeration<String> dexEntries = dexfile.entries();
                     while (dexEntries.hasMoreElements()) {
                         String className = dexEntries.nextElement();
                         //如果是以 com.alibaba.android.arouter.routes 开头的就加入classNames中
                         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;
 }

我们看如何获取所有的资源路径

 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<>();
         //添加默认的apk地址
         sourcePaths.add(applicationInfo.sourceDir);
 ​
         //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()) {
             //如果启用了MultiDex 说明有多个包
             //the total dex numbers
             //从这个Preferences中获取dex的数量
             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中
                     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;
     }

到目前位置我们就已经拿到了所有的className 。之后我们就可以用这些className反射创建调用内部的方法

获取(调用时)

接下来我们查看具体的调用

 ARouter.getInstance().build(path)
         .withString("name", "小度小度")
         .navigation()

先看build

 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);
     }
 }
 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);
             }
         }
         
         //这里会把当前的path 与 group存起来
         //path = /app/home  group = app
         return new Postcard(path, group);
     }
 }

上面的build生成了一个Postcard函数

withString其实就是往Postcard中的Bundle中存数据

紧接着 我们看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;
     }
 ​
     // 默认取自己的如果没有就是全局的context
     postcard.setContext(null == context ? mContext : context);
 ​
     try {
         //这里完成明信片配置
         LogisticsCenter.completion(postcard);
     } catch (NoRouteFoundException ex) {
         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()) {   
         //执行拦截器处理
         interceptorService.doInterceptions(postcard, new InterceptorCallback() {
             /**
              * Continue process
              *
              * @param postcard route meta
              */
             @Override
             public void onContinue(Postcard postcard) {
                 //我们的Activity跳转就在这里
                 _navigation(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(postcard, requestCode, callback);
     }
 ​
     return null;
 }

我们先看postcard的配置

 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) {
         //判断有没有这个组
         if (!Warehouse.groupsIndex.containsKey(postcard.getGroup())) {
             throw new NoRouteFoundException(TAG + "There is no route match the path [" + postcard.getPath() + "], in group [" + postcard.getGroup() + "]");
         } else {
             try {
                 //这里执行 生成类 Group的方法
                 addRouteGroupDynamic(postcard.getGroup(), null);
             } catch (Exception e) {
                 throw new HandlerException(TAG + "Fatal exception when loading group meta. [" + e.getMessage() + "]");
             }
             
             //重新执行一次 ,第二次RouteMeta不为空了
             completion(postcard);   // Reload
         }
     } else {
         //第二次 对postcard进行赋值
         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) {
                         logger.error(TAG, "Init provider failed!", e);
                         throw new HandlerException("Init provider failed!");
                     }
                 }
                 postcard.setProvider(instance);
                 //这里 Provider 或者 Fragment 会直接设置绿色通道 不走拦截器
                 postcard.greenChannel();    // Provider should skip all of interceptors
                 break;
             case FRAGMENT:
                 postcard.greenChannel();    // Fragment needn't interceptors
             default:
                 break;
         }
     }
 }

我们看一看Group的解析

 public synchronized static void addRouteGroupDynamic(String groupName, IRouteGroup group) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
     if (Warehouse.groupsIndex.containsKey(groupName)){
         //通过app 获取到ARouter$$Group$$app这个类 ,然后反射调用loadInto方法
         Warehouse.groupsIndex.get(groupName).getConstructor().newInstance().loadInto(Warehouse.routes);
         //删除
         Warehouse.groupsIndex.remove(groupName);
     }
 ​
     // cover old group.
     if (null != group) {
         group.loadInto(Warehouse.routes);
     }
 }
 ​
 ​
 public class ARouter$$Group$$app implements IRouteGroup {
   @Override
   public void loadInto(Map<String, RouteMeta> atlas) {
     //将path内容放入map中
     atlas.put("/app/home", RouteMeta.build(RouteType.ACTIVITY, HomeActivity.class, "/app/home", "app", new java.util.HashMap<String, Integer>(){{put("name", 8); }}, -1, -2147483648));
   }
 }

到这里就 completion就完成了

接着我们开始_navigation

 private Object _navigation(final Postcard postcard, final int requestCode, final NavigationCallback callback) {
     final Context currentContext = postcard.getContext();
 ​
     switch (postcard.getType()) {
         //我们执行的就是Activity
         case ACTIVITY:
             // Build intent
             final Intent intent = new Intent(currentContext, postcard.getDestination());
             intent.putExtras(postcard.getExtras());
 ​
             // Set flags.
             int flags = postcard.getFlags();
             if (0 != flags) {
                 intent.setFlags(flags);
             }
 ​
             // Non activity, need FLAG_ACTIVITY_NEW_TASK
             if (!(currentContext instanceof Activity)) {
                 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
             }
 ​
             // Set Actions
             String action = postcard.getAction();
             if (!TextUtils.isEmpty(action)) {
                 intent.setAction(action);
             }
 ​
             // Navigation in main looper.
             //上面从postcard中取出参数 然后在主线程中完成跳转
             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;
 }

到这里一个基础的Activity跳转就分析完成了

拓展

我们在开发中经常会自动注入参数的功能

 @Autowired
 @JvmField
 var name: String? = null
 ​
 ARouter.getInstance().inject(this)

实现原理

它会在编译期生成一个文件 ,内部从Intent中获取数据

 public class HomeActivity$$ARouter$$Autowired implements ISyringe {
   private SerializationService serializationService;
 ​
   @Override
   public void inject(Object target) {
     serializationService = ARouter.getInstance().navigation(SerializationService.class);
     //这就是Inject中传过来的参数
     HomeActivity substitute = (HomeActivity)target;
     //调用本类的getIntent()
     substitute.name = substitute.getIntent().getExtras() == null ? substitute.name : substitute.getIntent().getExtras().getString("name", substitute.name);
   }
 }

我们看一看调用

 static void inject(Object thiz) {
     AutowiredService autowiredService = ((AutowiredService) 
 //构建了一个path为 /arouter/service/autowired的路由 ,然后navigation
   ARouter.getInstance().build("/arouter/service/autowired").navigation());
     if (null != autowiredService) {
         autowiredService.autowire(thiz);
     }
 }

在这之前,我们需要回到init

我们还记得上面提到的 生成文件的全路径为 com.alibaba.android.arouter.routes.ARouter$$Root$$app

其实ARoute自己内部也有一个文件 com.alibaba.android.arouter.routes.ARouter$$Root$$arouterapi

我们看一看它的文件

 public class ARouter$$Providers$$arouterapi implements IProviderGroup {
     public ARouter$$Providers$$arouterapi() {
     }
 ​
     public void loadInto(Map<String, RouteMeta> providers) {
         //会放入一个AutowiredServiceImpl 与 InterceptorServiceImpl放到我们的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));
     }
 }
 ​
 ​
 //这是group文件
 public class ARouter$$Group$$arouter implements IRouteGroup {
     public ARouter$$Group$$arouter() {
     }
 ​
     public void loadInto(Map<String, RouteMeta> atlas) {
         //这个地方熟悉不 /arouter/service/autowired 就是我们inject时填入的path 早已存在了
         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));
     }
 }
 ​
 ​
 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);
     }
 }

这里应该大家就明白了 ,ARoute默认放入了它自己的文件在对应的目录下,当生成文件执行的时候 将这个默认文件也执行

这样就可以提前将AutowiredServiceImpl类型的参数放到RouteMeta

下面我们接着分析,我们这里获取到了实现类 ,调用autowire方法

 public void autowire(Object instance) {
     doInject(instance, null);
 }
 private void doInject(Object instance, Class<?> parent) {
     Class<?> clazz = null == parent ? instance.getClass() : parent;
     
     //获取到我们上面生成的类 
     //这个 getSyringe 这里用了缓存,从缓存取出对象
     ISyringe syringe = getSyringe(clazz);
     if (null != syringe) {
         //执行inject 也就是我们的生成类被执行
         syringe.inject(instance);
     }
 ​
     Class<?> superClazz = clazz.getSuperclass();
     // has parent and its not the class of framework.
     if (null != superClazz && !superClazz.getName().startsWith("android")) {
         doInject(instance, superClazz);
     }
 }

这里一切就清楚了

总结

总体的步骤分为以下几步

  1. 生成对应的route文件 ,root对应group,也就是 app对应 /app/home …等一堆path
  2. init的时候 通过package取出所有的 dex文件 然后获取到className,最后通过反射 调用Class内部的方法 ,也就是生成类中的方法
  3. build的时候 ,获取当前path对应的参数 装入到postcard
  4. navigation的时候, 拿到postcard解析出所有的参数 ,进行组装跳转

最后文章中有问题的地方,欢迎大家多多评论指正,提前祝所有观看文章的朋友们新年快乐 😉

参考链接: Arouter从使用到原理