彙總篇章:
簡介
組件化被越來越多的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的數據
由於篇幅緣故,故剩下的兩步我們拆成新篇章處理