ARoute初步探究(二):路由定位過(guò)程

上一篇文章我們記錄了在編譯期ARoute將目標(biāo)注解生成文件保存的過(guò)程,這一節(jié)我們來(lái)探究下在用戶使用時(shí)ARoute做了什么

兩條路徑保證

在官方README中我們能看見(jiàn)關(guān)于AutoRegister的介紹,借由這個(gè)gradle插件能實(shí)現(xiàn)路由表的自動(dòng)加載。這節(jié)我們先不講這個(gè),關(guān)心下普通狀態(tài)下的加載過(guò)程。

前期準(zhǔn)備工作

初始化

在使用ARoute之前,我們需要進(jìn)行初始化ARouter.init,初始化期間會(huì)將儲(chǔ)存信息讀取到靜態(tài)變量中,使我們能順序完成跳轉(zhuǎn),因此先來(lái)看下初始化的內(nèi)容:
ARoute的初始化代碼:

 public static void init(Application application) {
           // 忽略不重要的過(guò)程
            hasInit = _ARouter.init(application);
    }
// _ARouter.init
protected static synchronized boolean init(Application application) {
        // 主要是進(jìn)行數(shù)據(jù)倉(cāng)庫(kù)初始化
        LogisticsCenter.init(mContext, executor);
}

初始化過(guò)程都是基操,維護(hù)一個(gè)主線程Handler用于線程切換、保存Context 等
接下來(lái)我們主要看下LogisticsCenter.init ,這里會(huì)進(jìn)行一次判斷

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

這里看到先做了一次預(yù)加載,如果是debug模式或者版本號(hào)升級(jí)的話預(yù)先做一次加載。但是使用版本號(hào)來(lái)判斷是否需要重新讀取組件,在未升級(jí)版本號(hào)的情況下可能會(huì)出現(xiàn)問(wèn)題。
getFileNameByPackageName這個(gè)函數(shù)的目的是讀取安裝包中現(xiàn)有的class,這些class都是以包名開(kāi)頭的,我們來(lái)看下他是如何實(shí)現(xiàn)的:

public static Set<String> getFileNameByPackageName(Context context, final String packageName) throws PackageManager.NameNotFoundException, IOException, InterruptedException {
        final Set<String> classNames = new HashSet<>();
        //獲取源文件路徑,先著重看下這個(gè)
        List<String> paths = getSourcePaths(context);
        、、、暫時(shí)忽略

public static List<String> getSourcePaths(Context context) throws PackageManager.NameNotFoundException, IOException {
        ApplicationInfo applicationInfo = context.getPackageManager().getApplicationInfo(context.getPackageName(), 0);
        // app 根目錄
        File sourceApk = new File(applicationInfo.sourceDir);

        List<String> sourcePaths = new ArrayList<>();
        // 應(yīng)用存放數(shù)據(jù)目錄
        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已經(jīng)支持了MultiDex,就不要去Secondary Folder加載 Classesx.zip了,那里已經(jīng)么有了
//        通過(guò)是否存在sp中的multidex.version是不準(zhǔn)確的,因?yàn)閺牡桶姹旧?jí)上來(lái)的用戶,是包含這個(gè)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;
    }

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

代碼中會(huì)到安裝目錄下讀取apk包( /data/app/com.alibaba.android.arouter.demo-S8YOQ-yxwK8cUikcaWvAVA==/base.apk),這里還有分包的可能

這里的是否支持分包isVMMultidexCapable是通過(guò)判斷版本號(hào)來(lái)決定的,version>2.1,這是一個(gè)可以學(xué)習(xí)的點(diǎn)。

拿到apk后,開(kāi)啟CountDownLatch,多線程查詢所有文件

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 " 

使用DexFile讀取文件,遍歷文件中的class,拿到所有包名開(kāi)頭的;
接下來(lái)就是根據(jù)讀到的信息,獲取三類我們想要的文件:IRouteRoot、IInterceptorGroup、IProviderGroup 讀取進(jìn)內(nèi)存;這里我們比較關(guān)心的是IRouteRoot,他就是我們上節(jié)提到的ARouter$$Root$$app,這里包含了所有我們的組信息:

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

初始化結(jié)束,我們的組信息儲(chǔ)存在Warehouse.groupsIndex

路由定位

最簡(jiǎn)單的形式:

  ARouter.getInstance().build("/test/activity2").navigation();  

build會(huì)生成一個(gè)PostCard對(duì)象
new Postcard(path, group),其中的組信息group也是根據(jù)簡(jiǎn)單的第一個(gè)/分割而來(lái)
接下來(lái)的navigation自然也是交給Postcard來(lái)處理了,事實(shí)上他只是充當(dāng)了數(shù)據(jù)model的作用,保存了組信息,調(diào)用轉(zhuǎn)發(fā)給了ARoute

ARouter.getInstance().navigation(context, this, -1, callback);

接下來(lái)交給數(shù)據(jù)倉(cāng)庫(kù)嘗試路由:

 try {
        LogisticsCenter.completion(postcard);
    }

首先會(huì)在已經(jīng)解析的Route信息中查找,如果找不到則到groupsIndex 中找對(duì)應(yīng)的組信息,然后實(shí)例化組信息記錄類,加載數(shù)據(jù):

 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:這里是重新調(diào)了一次自身
            }
        }

在找到的情況下,填充自身數(shù)據(jù):

// 比較重要的是這個(gè),記錄目標(biāo)class
  postcard.setDestination(routeMeta.getDestination());
  postcard.setType(routeMeta.getType());
  postcard.setPriority(routeMeta.getPriority());
  postcard.setExtra(routeMeta.getExtra());

接下來(lái)回到_ARoute中,找到記錄就告知上層:

if (null != callback) {
    callback.onFound(postcard);
}

然后是實(shí)際的跳轉(zhuǎn):

return _navigation(context, postcard, requestCode, callback);

我們看下Activity的處理情況:

case ACTIVITY:
    // Build intent
    final Intent intent = new Intent(currentContext, postcard.getDestination());
    // 我們?cè)谕鈱诱{(diào)用withString等方法傳遞參數(shù)的時(shí)候,就會(huì)保存到PostCard的mBundle中
    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.
        // 應(yīng)該有很多朋友遇到這種奔潰,非Activity源啟動(dòng)需要加上New_Task標(biāo)志
        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;

這段代碼不難看明白,基本是我們平時(shí)跳轉(zhuǎn)的基操。

課后題:通過(guò)插件掃描dex是怎么實(shí)現(xiàn)的

LogisticsCenter有這么一個(gè)方法loadRouterMap,我們?cè)谡{(diào)初始化的時(shí)候會(huì)調(diào)用它,所以可否在這里入手呢?
com.alibaba.arouter注冊(cè)的插件PluginLaunch會(huì)執(zhí)行 RegisterTransform的轉(zhuǎn)換操作,經(jīng)過(guò)層層調(diào)用,最終是使用asm來(lái)修改現(xiàn)有代碼的,RouteMethodVisitor會(huì)修改LogisticsCenter. loadRouterMap 在其中注入 LogisticsCenter. register方法,把掃描到的目標(biāo)類IRoute作為參數(shù)注入。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • ARouter探究(一) 前言 ARouter 是 Alibaba 開(kāi)源的一款 Android 頁(yè)面路由框架,特別...
    Jason騎蝸牛看世界閱讀 1,359評(píng)論 1 3
  • 前言 隨著項(xiàng)目業(yè)務(wù)邏輯和功能點(diǎn)日益遞增, 邏輯的耦合程度也逐漸升高, 組件化技術(shù)可以很好的解決這個(gè)問(wèn)題, 公司大佬...
    SharryChoo閱讀 1,119評(píng)論 0 9
  • 開(kāi)發(fā)一款A(yù)pp,總會(huì)遇到各種各樣的需求和業(yè)務(wù),這時(shí)候選擇一個(gè)簡(jiǎn)單好用的輪子,就可以事半功倍 前言 上面一段代碼,在...
    WangDeFa閱讀 65,878評(píng)論 44 198
  • 不太久遠(yuǎn)的世界 就我現(xiàn)在的年齡和閱歷,談世界顯得非常妄自尊大,但這件事情并不是不可以,畢竟,這個(gè)只單單屬于自己,...
    趨于空白后的遐想閱讀 177評(píng)論 0 0
  • 01 我叫紀(jì)陽(yáng)光,出生在一座被雨淹沒(méi)的日光城市。 聽(tīng)媽媽說(shuō),我們生活的這座城市不知因?yàn)槭裁丛颍幸蝗仗?yáng)突然就消...
    海邊小公主閱讀 220評(píng)論 0 1