ARouter路由使用与源码分析(一)

汇总篇章:

ARouter路由源码分析汇总

简介

组件化被越来越多的Android项目采用,而作为组件化的基础——路由也是重中之重,如果说组件化是肢体,那么路由就是缝合各个组件的筋脉,废话不多说,既然重中之重,那么从源码层次了解ARouter的设计,以及借鉴ARouter的设计来设计自己组件中的ARouter

源码总线

ARouter的源码分析的分析路线,就顺着使用顺序分成:

  • 第一步:添加页面注解
  • 第二步:初始化ARouter的SDK
  • 第三步:发起路由操作
  • 第四步:跳转过程监听和参数解析

,来逐步对ARouter分析

依赖这里就附上官网链接:https://github.com/alibaba/ARouter/blob/master/README_CN.md

第一步:添加页面注解

// 在支持路由的页面上添加注解(必选)
// 这里的路径需要注意的是至少需要有两级,/xx/xx
@Route(path = "/test/activity")
public class YourActivity extend Activity {
    ...
}

这一步,由于这步处理依赖基础配置等,故我们在第四步的参数解析里面展开处理

第二步:初始化ARouter的SDK

ARouter.init(mApplication);

我们先从ARouter的初始化方法init这个入口开始分析ARouter的分析之旅

		/**
     * Init, it must be call before used router.
     */
    public static void init(Application application) {
      /**
       * hasInit避免多次初始化
       */
        if (!hasInit) {
            logger = _ARouter.logger;
            _ARouter.logger.info(Consts.TAG, "ARouter init start.");
          	/**
             * 初始化_ARouter实际实现类
             */
            hasInit = _ARouter.init(application);

            if (hasInit) {
                _ARouter.afterInit();
            }

            _ARouter.logger.info(Consts.TAG, "ARouter init over.");
        }
    }

#####实现类_ARouter的init方法

protected static synchronized boolean init(Application application) {
        mContext = application;
        LogisticsCenter.init(mContext, executor);
        logger.info(Consts.TAG, "ARouter init success!");
        hasInit = true;
        return true;
    }
在_ARouter的init方法中:
1) 对上下文的处理
2) 提及LogisticsCenter的init的初始化操作
3) 打印log,初始化标记维护

对于LogisticsCenter这个类是个非常重要的类,LogisticsCenter的功能分为3大功能:

1. 单例处理
2. 处理关系映射
3. 解决重复定义组逻辑

我们顺着init方法继续分析,由于init方法过长,这里拆分局部分析:

在这里插入图片描述

首先进入先分析第一个方法loadRouterMap,从名字上就能感觉这里是加载router映射的方法,但是进入loadRouterMap方法中

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());
    }
这个方法体是空的,但是从注释上我们了解到这个方法是自动注册插件生成代码使用的, 调用这个方法的目的是注册所有路由器、侦听器等,这块我们方法后面的插件代码分析的篇章来详细描述这个方法

接下来我们看到方法维护了一个名为routerMap的Set集合,我们根据条件来逐步分析

  • ARouter的debugger情况下,对routerMap的填充

    从方法上对routerMap的填充放在ClassUtils.getFileNameByPackageName(mContext, ROUTE_ROOT_PAKCAGE);

    在ClassUtils的getFileNameByPackageName的方法中:
    在这里插入图片描述

    首先通过getSourcePaths获取路径信息,getSourcePaths方法如下:
      public static List<String> getSourcePaths(Context context) throws PackageManager.NameNotFoundException, IOException {
      			/**
      			 * 首先通过PackageManager获取applicationinfo信息
      			 * 并记录applicationinfo的sourceDir
      			 */
            ApplicationInfo applicationInfo = context.getPackageManager().getApplicationInfo(context.getPackageName(), 0);
            File sourceApk = new File(applicationInfo.sourceDir);
    
            List<String> sourcePaths = new ArrayList<>();
      			/**
      			 * 添加默认的apk路径
      			 */
            sourcePaths.add(applicationInfo.sourceDir);
    
            //提取class文件的前缀信息
            String extractedFilePrefix = sourceApk.getName() + EXTRACTED_NAME_EXT;
    
    //        如果VM已经支持了MultiDex,就不要去Secondary Folder加载 Classesx.zip了,那里已经么有了
    //        通过是否存在sp中的multidex.version是不准确的,因为从低版本升级上来的用户,是包含这个sp配置的
            if (!isVMMultidexCapable()) {
                //总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()) {
                      	/**
                      	 * 将所有的apk路径
                      	 */
                        sourcePaths.add(extractedFile.getAbsolutePath());
                    } else {
                        throw new IOException("Missing extracted secondary dex file '" + extractedFile.getPath() + "'");
                    }
                }
            }
    
            if (ARouter.debuggable()) {
              	/**
              	 * 如果是ARouter处于debugger情况下,这块是尝试加载InstantRun的文件路径
              	 */
                sourcePaths.addAll(tryLoadInstantRunDexFile(applicationInfo));
            }
            return sourcePaths;
        }
    
    tryLoadInstantRunDexFile方法如下:
    private static List<String> tryLoadInstantRunDexFile(ApplicationInfo applicationInfo) {
            List<String> instantRunSourcePaths = new ArrayList<>();
    
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && null != applicationInfo.splitSourceDirs) {
               	/**
               	 * 新版本中InstantRun通过applicationInfo.splitSourceDirs直接添加
               	 */
                instantRunSourcePaths.addAll(Arrays.asList(applicationInfo.splitSourceDirs));
                Log.d(Consts.TAG, "Found InstantRun support");
            } else {
                try {
                  /**
                   * 这个是google的instant run的sdk中获取dex的files路径
                   */
                    Class pathsByInstantRun = Class.forName("com.android.tools.fd.runtime.Paths");
                    Method getDexFileDirectory = pathsByInstantRun.getMethod("getDexFileDirectory", String.class);
                    String instantRunDexPath = (String) getDexFileDirectory.invoke(null, applicationInfo.packageName);
    
                    File instantRunFilePath = new File(instantRunDexPath);
                    if (instantRunFilePath.exists() && instantRunFilePath.isDirectory()) {
                        File[] dexFile = instantRunFilePath.listFiles();
                        for (File file : dexFile) {
                            if (null != file && file.exists() && file.isFile() && file.getName().endsWith(".dex")) {
                                instantRunSourcePaths.add(file.getAbsolutePath());
                            }
                        }
                    }
    
                } catch (Exception e) {
                    Log.e(Consts.TAG, "InstantRun support error, " + e.getMessage());
                }
            }
    
            return instantRunSourcePaths;
        }
    

    获取了所有的path之后,在getFileNameByPackageName接着起了一个线程在这里插入图片描述

    在这个线程池中,通过DexFile对class加载,然后所有classname放起来,返回给我们routerMap,到这里routerMap中存放的就是我们所有classname

  • 接着我们看看非debugger情况下ARouter的处理

    在这里插入图片描述

    首先从sharedPreference名为SP_AROUTER_CACHE中读取routerMap的本地缓存并初始化我们容器routerMap

    接着对routerMap进行分类处理

    这里的部分,我们在后面的插件部分详细解释
    第一类情况:
      com.alibaba.android.arouter.routes.ARouter$$Root
        启动RouteRoot核心类
    第二类情况:
      com.alibaba.android.arouter.routes.ARouter$$Interceptors
        加载相关interceptor的数据
    第三类情况:
    	com.alibaba.android.arouter.routes.ARouter$$Providers
        加载相关service的数据
    

由于篇幅缘故,故剩下的两步我们拆成新篇章处理

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章