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的數據
    

由於篇幅緣故,故剩下的兩步我們拆成新篇章處理

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