Tinker的補(bǔ)丁加載-dex加載

Tinker和Instant Run的并存
Tinker 非代理模式的啟動(dòng)
Tinker的啟動(dòng)流程

加載補(bǔ)丁入口

前面介紹到了加載補(bǔ)丁的地方主要在TinkerLoader的tryLoad方法中当宴,代碼如下:

private void loadTinker() {
        try {
            //reflect tinker loader, because loaderClass may be define by user!
            Class<?> tinkerLoadClass = Class.forName(loaderClassName, false, TinkerApplication.class.getClassLoader());
            Method loadMethod = tinkerLoadClass.getMethod(TINKER_LOADER_METHOD, TinkerApplication.class);
            Constructor<?> constructor = tinkerLoadClass.getConstructor();
            tinkerResultIntent = (Intent) loadMethod.invoke(constructor.newInstance(), this);
        } catch (Throwable e) {
            //has exception, put exception error code
            tinkerResultIntent = new Intent();
            ShareIntentUtil.setIntentReturnCode(tinkerResultIntent, ShareConstants.ERROR_LOAD_PATCH_UNKNOWN_EXCEPTION);
            tinkerResultIntent.putExtra(INTENT_PATCH_EXCEPTION, e);
        }
    }

loaderClassName這個(gè)方法是manifest中的真正的application中的創(chuàng)建時(shí)傳遞的類。一般情況是com.tencent.tinker.loader.TinkerLoader這個(gè)類倍阐,通過(guò)反射創(chuàng)建這個(gè)類之后,調(diào)用TINKER_LOADER_METHOD即tryLoad這個(gè)方法课兄。之前我們直接跳過(guò)了tryLoad 這個(gè)方法分析應(yīng)用的啟動(dòng)渺贤,現(xiàn)在分析下tryLoad這個(gè)加載補(bǔ)丁的流程,tryLoad及之后的流程才是tinker的關(guān)鍵所在颅围。

patch文件的檢查

TinkerLoader繼承自抽象類AbstractTinkerLoader皱卓,AbstractTinkerLoader只有一個(gè)抽象方法tryLoad裹芝。TinkerLoader實(shí)現(xiàn)了tryLoad方法,這個(gè)方法中主要調(diào)用了tryLoadPatchFilesInternal娜汁,這個(gè)方法執(zhí)行主要的邏輯嫂易。主要代碼如下:

private void tryLoadPatchFilesInternal(TinkerApplication app, Intent resultIntent) {
    final int tinkerFlag = app.getTinkerFlags();// 在RealApplication中傳入的參數(shù)
     ......省略部分異常檢查代碼,檢查包括是否開(kāi)啟tinker,是否非patch進(jìn)程掐禁,patch文件是否存在等怜械。
    //tinker/patch.info
    //1 獲取patch.info.有點(diǎn)疑問(wèn),這個(gè)patch.info文件是什么時(shí)候放到這個(gè)目錄下的傅事?文件內(nèi)容是什么缕允?
    File patchInfoFile = SharePatchFileUtil.getPatchInfoFile(patchDirectoryPath);

    //old = 641e634c5b8f1649c75caf73794acbdf
    //new = 2c150d8560334966952678930ba67fa8
    File patchInfoLockFile = SharePatchFileUtil.getPatchInfoLockFile(patchDirectoryPath);
    //2 eadAndCheckPropertyWithLock有點(diǎn)玄機(jī),lock 應(yīng)該是和同步相關(guān)蹭越,利用文件實(shí)現(xiàn)的同步灼芭?
    //讀取patch.info文件中的內(nèi)容
    patchInfo = SharePatchInfo.readAndCheckPropertyWithLock(patchInfoFile, patchInfoLockFile);
    if (patchInfo == null) {
        ShareIntentUtil.setIntentReturnCode(resultIntent, ShareConstants.ERROR_LOAD_PATCH_INFO_CORRUPTED);
        return;
    }
    ......
        
    //patch-641e634c
    String patchName = SharePatchFileUtil.getPatchVersionDirectory(version);
    //tinker/patch.info/patch-641e634c
    String patchVersionDirectory = patchDirectoryPath + "/" + patchName;
    File patchVersionDirectoryFile = new File(patchVersionDirectory);
    //tinker/patch.info/patch-641e634c/patch-641e634c.apk
    final String patchVersionFileRelPath = SharePatchFileUtil.getPatchVersionFile(version);
    File patchVersionFile = (patchVersionFileRelPath != null ? new File(patchVersionDirectoryFile.getAbsolutePath(), patchVersionFileRelPath) : null);

    ShareSecurityCheck securityCheck = new ShareSecurityCheck(app);
    // 3 對(duì)patch文件進(jìn)行檢查,包括tinkerId,以及patch中的文件般又。
    int returnCode = ShareTinkerInternals.checkTinkerPackage(app, tinkerFlag, patchVersionFile, securityCheck);
   
    resultIntent.putExtra(ShareIntentUtil.INTENT_PATCH_PACKAGE_CONFIG, securityCheck.getPackagePropertiesIfPresent());

    final boolean isEnabledForDex = ShareTinkerInternals.isTinkerEnabledForDex(tinkerFlag);
    final boolean isEnabledForNativeLib = ShareTinkerInternals.isTinkerEnabledForNativeLib(tinkerFlag);
    //check resource
    final boolean isEnabledForResource = ShareTinkerInternals.isTinkerEnabledForResource(tinkerFlag);
    
     //only work for art platform oat,because of interpret, refuse 4.4 art oat
        //android o use quicken default, we don't need to use interpret mode
        boolean isSystemOTA = ShareTinkerInternals.isVmArt()
            && ShareTinkerInternals.isSystemOTA(patchInfo.fingerPrint)
            && Build.VERSION.SDK_INT >= 21 && !ShareTinkerInternals.isAfterAndroidO();

        resultIntent.putExtra(ShareIntentUtil.INTENT_PATCH_SYSTEM_OTA, isSystemOTA);
 .......   
}

tryLoadPatchFilesInternal這個(gè)方法非常繁瑣巍佑,主要是能否進(jìn)行patch的前提進(jìn)行檢查茴迁,包括是否開(kāi)啟tinker功能,是否在非patch進(jìn)程萤衰,是否存在patch文件堕义,以及檢查patch文件是否符合要求等。另外檢查過(guò)程中有個(gè)疑問(wèn),patch.info文件是什么是創(chuàng)建的倦卖? 這個(gè)答案需要我們來(lái)解答洒擦。
接下來(lái)看tryLoadPatchFilesInternal的剩余一部分,代碼如下:

private void tryLoadPatchFilesInternal(TinkerApplication app, Intent resultIntent) {
        final boolean isEnabledForDex = ShareTinkerInternals.isTinkerEnabledForDex(tinkerFlag);
        // 判斷邏輯中有“com.huawei.ark.app.ArkApplicationInfo”的類名怕膛,貌似是華為的方舟熟嫩,后續(xù)先認(rèn)為isArkHotRuning結(jié)果都是false
        final boolean isArkHotRuning = ShareTinkerInternals.isArkHotRuning();

        if (!isArkHotRuning && isEnabledForDex) {
            //tinker/patch.info/patch-641e634c/dex
            boolean dexCheck = TinkerDexLoader.checkComplete(patchVersionDirectory, securityCheck, oatDex, resultIntent);
            if (!dexCheck) {
                //file not found, do not load patch
                Log.w(TAG, "tryLoadPatchFiles:dex check fail");
                return;
            }
        }

        final boolean isEnabledForArkHot = ShareTinkerInternals.isTinkerEnabledForArkHot(tinkerFlag);
        if (isArkHotRuning && isEnabledForArkHot) {
            boolean arkHotCheck = TinkerArkHotLoader.checkComplete(patchVersionDirectory, securityCheck, resultIntent);
            if (!arkHotCheck) {
                // file not found, do not load patch
                Log.w(TAG, "tryLoadPatchFiles:dex check fail");
                return;
            }
        }

        final boolean isEnabledForNativeLib = ShareTinkerInternals.isTinkerEnabledForNativeLib(tinkerFlag);
        if (isEnabledForNativeLib) {
            //tinker/patch.info/patch-641e634c/lib
            boolean libCheck = TinkerSoLoader.checkComplete(patchVersionDirectory, securityCheck, resultIntent);
            if (!libCheck) {
                //file not found, do not load patch
                Log.w(TAG, "tryLoadPatchFiles:native lib check fail");
                return;
            }
        }

        //check resource
        final boolean isEnabledForResource = ShareTinkerInternals.isTinkerEnabledForResource(tinkerFlag);
        Log.w(TAG, "tryLoadPatchFiles:isEnabledForResource:" + isEnabledForResource);
        if (isEnabledForResource) {
            boolean resourceCheck = TinkerResourceLoader.checkComplete(app, patchVersionDirectory, securityCheck, resultIntent);
            if (!resourceCheck) {
                //file not found, do not load patch
                Log.w(TAG, "tryLoadPatchFiles:resource check fail");
                return;
            }
        }
        //only work for art platform oat,because of interpret, refuse 4.4 art oat
        //android o use quicken default, we don't need to use interpret mode
        boolean isSystemOTA = ShareTinkerInternals.isVmArt()
            && ShareTinkerInternals.isSystemOTA(patchInfo.fingerPrint)
            && Build.VERSION.SDK_INT >= 21 && !ShareTinkerInternals.isAfterAndroidO();

        resultIntent.putExtra(ShareIntentUtil.INTENT_PATCH_SYSTEM_OTA, isSystemOTA);

        //we should first try rewrite patch info file, if there is a error, we can't load jar
        if (mainProcess) {
            if (versionChanged) {
                patchInfo.oldVersion = version;
            }
            if (oatModeChanged) {
                patchInfo.oatDir = oatDex;
                // delete interpret odex
                // for android o, directory change. Fortunately, we don't need to support android o interpret mode any more
                Log.i(TAG, "tryLoadPatchFiles:oatModeChanged, try to delete interpret optimize files");
                SharePatchFileUtil.deleteDir(patchVersionDirectory + "/" + ShareConstants.INTERPRET_DEX_OPTIMIZE_PATH);
            }
        }

        if (!checkSafeModeCount(app)) {
            resultIntent.putExtra(ShareIntentUtil.INTENT_PATCH_EXCEPTION, new TinkerRuntimeException("checkSafeModeCount fail"));
            ShareIntentUtil.setIntentReturnCode(resultIntent, ShareConstants.ERROR_LOAD_PATCH_UNCAUGHT_EXCEPTION);
            Log.w(TAG, "tryLoadPatchFiles:checkSafeModeCount fail");
            return;
        }

        //now we can load patch jar
        if (!isArkHotRuning && isEnabledForDex) {
            boolean loadTinkerJars = TinkerDexLoader.loadTinkerJars(app, patchVersionDirectory, oatDex, resultIntent, isSystemOTA, isProtectedApp);

            if (isSystemOTA) {
                // update fingerprint after load success
                patchInfo.fingerPrint = Build.FINGERPRINT;
                patchInfo.oatDir = loadTinkerJars ? ShareConstants.INTERPRET_DEX_OPTIMIZE_PATH : ShareConstants.DEFAULT_DEX_OPTIMIZE_PATH;
                // reset to false
                oatModeChanged = false;

                if (!SharePatchInfo.rewritePatchInfoFileWithLock(patchInfoFile, patchInfo, patchInfoLockFile)) {
                    ShareIntentUtil.setIntentReturnCode(resultIntent, ShareConstants.ERROR_LOAD_PATCH_REWRITE_PATCH_INFO_FAIL);
                    Log.w(TAG, "tryLoadPatchFiles:onReWritePatchInfoCorrupted");
                    return;
                }
                // update oat dir
                resultIntent.putExtra(ShareIntentUtil.INTENT_PATCH_OAT_DIR, patchInfo.oatDir);
            }
            if (!loadTinkerJars) {
                Log.w(TAG, "tryLoadPatchFiles:onPatchLoadDexesFail");
                return;
            }
        }

        if (isArkHotRuning && isEnabledForArkHot) {
            boolean loadArkHotFixJars = TinkerArkHotLoader.loadTinkerArkHot(app, patchVersionDirectory, resultIntent);
            if (!loadArkHotFixJars) {
                Log.w(TAG, "tryLoadPatchFiles:onPatchLoadArkApkFail");
                return;
            }
        }
     ......
    }

在tryLoadPatchFilesInternal這個(gè)方法的第二部分分別對(duì)各項(xiàng)標(biāo)志位進(jìn)行賦值褐捻,包括是否支持dex進(jìn)行修復(fù)掸茅,是否支持對(duì)資源文件進(jìn)行修復(fù),是否支持對(duì)native文件進(jìn)行修復(fù)等柠逞。其中還有sSystemOTA判斷昧狮,只要用戶是ART環(huán)境并且做了OTA升級(jí),則在加載dex補(bǔ)丁的時(shí)候板壮,就會(huì)先把最近一次的補(bǔ)丁全部DexFile.loadDex一遍逗鸣。這么做的原因是有些場(chǎng)景做了OTA后,oat的規(guī)則可能發(fā)生變化绰精,在這種情況下去加載上個(gè)系統(tǒng)版本oat過(guò)的dex就會(huì)出現(xiàn)問(wèn)題撒璧。由此可見(jiàn),將東西做好的代價(jià)有點(diǎn)大茬底』Ρ可惟其如此,才有真正的價(jià)值阱表。這里先假設(shè)所有的檢查都是通過(guò)殿如,先看主流程。檢查之后就是開(kāi)始加載的過(guò)程最爬。

開(kāi)始加載

private void tryLoadPatchFilesInternal(TinkerApplication app, Intent resultIntent) {
    ......
     //now we can load patch jar
        if (!isArkHotRuning && isEnabledForDex) {
            boolean loadTinkerJars = TinkerDexLoader.loadTinkerJars(app, patchVersionDirectory, oatDex, resultIntent, isSystemOTA, isProtectedApp);
            if (isSystemOTA) {
                // update fingerprint after load success
                patchInfo.fingerPrint = Build.FINGERPRINT;
                patchInfo.oatDir = loadTinkerJars ? ShareConstants.INTERPRET_DEX_OPTIMIZE_PATH : ShareConstants.DEFAULT_DEX_OPTIMIZE_PATH;
                // reset to false
                oatModeChanged = false;

                if (!SharePatchInfo.rewritePatchInfoFileWithLock(patchInfoFile, patchInfo, patchInfoLockFile)) {
                    ShareIntentUtil.setIntentReturnCode(resultIntent, ShareConstants.ERROR_LOAD_PATCH_REWRITE_PATCH_INFO_FAIL);
                    Log.w(TAG, "tryLoadPatchFiles:onReWritePatchInfoCorrupted");
                    return;
                }
                // update oat dir
                resultIntent.putExtra(ShareIntentUtil.INTENT_PATCH_OAT_DIR, patchInfo.oatDir);
            }
            if (!loadTinkerJars) {
                Log.w(TAG, "tryLoadPatchFiles:onPatchLoadDexesFail");
                return;
            }
        }

        if (isArkHotRuning && isEnabledForArkHot) {
            boolean loadArkHotFixJars = TinkerArkHotLoader.loadTinkerArkHot(app, patchVersionDirectory, resultIntent);
            if (!loadArkHotFixJars) {
                Log.w(TAG, "tryLoadPatchFiles:onPatchLoadArkApkFail");
                return;
            }
        }

        //now we can load patch resource
        if (isEnabledForResource) {
            boolean loadTinkerResources = TinkerResourceLoader.loadTinkerResources(app, patchVersionDirectory, resultIntent);
            if (!loadTinkerResources) {
                Log.w(TAG, "tryLoadPatchFiles:onPatchLoadResourcesFail");
                return;
            }
        }

        // Init component hotplug support.
        if ((isEnabledForDex || isEnabledForArkHot) && isEnabledForResource) {
            ComponentHotplug.install(app, securityCheck);
        }

        // Before successfully exit, we should update stored version info and kill other process
        // to make them load latest patch when we first applied newer one.
        if (mainProcess && versionChanged) {
            //update old version to new
            if (!SharePatchInfo.rewritePatchInfoFileWithLock(patchInfoFile, patchInfo, patchInfoLockFile)) {
                ShareIntentUtil.setIntentReturnCode(resultIntent, ShareConstants.ERROR_LOAD_PATCH_REWRITE_PATCH_INFO_FAIL);
                Log.w(TAG, "tryLoadPatchFiles:onReWritePatchInfoCorrupted");
                return;
            }

            ShareTinkerInternals.killProcessExceptMain(app);
        }

        //all is ok!
        ShareIntentUtil.setIntentReturnCode(resultIntent, ShareConstants.ERROR_LOAD_OK);
        Log.i(TAG, "tryLoadPatchFiles: load end, ok!");
        return;
}

加載補(bǔ)丁的方法因?yàn)槎鄠€(gè)參數(shù)而有所區(qū)別涉馁,這里我們重點(diǎn)看下loadTinkerJars()和loadTinkerResources這兩個(gè)方法。最后加載成功后將patch的版本更新到文件爱致,然后將其他進(jìn)程都?xì)⒌艨舅停詈髮atch成功的信息返回。如果tinker更新成功之后糠悯,下次啟動(dòng)是否還需要進(jìn)行patch的過(guò)程呢帮坚?按道理是不需要的,此處更新了版本信息應(yīng)該是出于這個(gè)目的互艾。熱更的兩個(gè)點(diǎn)一個(gè)是代碼更新试和,一個(gè)是資源更新,分別對(duì)應(yīng)于loadTinkerJars和loadTinkerResources這兩個(gè)方法纫普,接下來(lái)我們分析這個(gè)兩個(gè)過(guò)程阅悍。

代碼加載

在loadTinkerJars方法里面調(diào)用了一些關(guān)于patch文件的信息,這些信息是在checkComplete中準(zhǔn)備好的,所以先看下checkComplete這個(gè)方法节视,代碼如下:

public static boolean checkComplete(String directory, ShareSecurityCheck securityCheck, String oatDir, Intent intentResult) {
        String meta = securityCheck.getMetaContentMap().get(DEX_MEAT_FILE);
        //not found dex
        if (meta == null) {
            return true;
        }
        LOAD_DEX_LIST.clear();
        classNDexInfo.clear();

        ArrayList<ShareDexDiffPatchInfo> allDexInfo = new ArrayList<>();
        // 解析patch文件中assets/dex_meta.txt的內(nèi)容
        ShareDexDiffPatchInfo.parseDexDiffPatchInfo(meta, allDexInfo);

        if (allDexInfo.isEmpty()) {
            return true;
        }

        HashMap<String, String> dexes = new HashMap<>();

        ShareDexDiffPatchInfo testInfo = null;

        for (ShareDexDiffPatchInfo info : allDexInfo) {
            //for dalvik, ignore art support dex
            if (isJustArtSupportDex(info)) {
                continue;
            }
            if (!ShareDexDiffPatchInfo.checkDexDiffPatchInfo(info)) {
                intentResult.putExtra(ShareIntentUtil.INTENT_PATCH_PACKAGE_PATCH_CHECK, ShareConstants.ERROR_PACKAGE_CHECK_DEX_META_CORRUPTED);
                ShareIntentUtil.setIntentReturnCode(intentResult, ShareConstants.ERROR_LOAD_PATCH_PACKAGE_CHECK_FAIL);
                return false;
            }
            if (isVmArt && info.rawName.startsWith(ShareConstants.TEST_DEX_NAME)) {
                testInfo = info;
            } else if (isVmArt && ShareConstants.CLASS_N_PATTERN.matcher(info.realName).matches()) {
                classNDexInfo.add(info);
            } else {
                dexes.put(info.realName, getInfoMd5(info));
                LOAD_DEX_LIST.add(info);
            }
        }

        if (isVmArt
            && (testInfo != null || !classNDexInfo.isEmpty())) {
            if (testInfo != null) {
                classNDexInfo.add(ShareTinkerInternals.changeTestDexToClassN(testInfo, classNDexInfo.size() + 1));
            }
            dexes.put(ShareConstants.CLASS_N_APK_NAME, "");
        }
        ...... 省略部分代碼
        //if is ok, add to result intent
        intentResult.putExtra(ShareIntentUtil.INTENT_PATCH_DEXES_PATH, dexes);
        return true;
    }

checkComplete中主要檢查patch中assets/dex_meta.txt文件的內(nèi)容拳锚,這個(gè)文件包含了patch中dex的名字,md5,crc校驗(yàn)等信息寻行。這個(gè)文件是在patch打包時(shí)生成霍掺。生成時(shí)機(jī)是在配置完成后,大概流程如下:

TinkerPatchPlugin.apply->Runner.inkerPatch->ApkDecoder.patch()->ApkFilesVisitor.visitFile()->UniqueDexDiffDecoder.patch()->DexDiffDecoder.onAllPatchesEnd()

patch文件如何生成寡痰,此處先按照上面這樣理解抗楔,后續(xù)再詳細(xì)分析。這里我們先回過(guò)來(lái)頭接著分析TinkerDexLoader的loadTinkerJars方法拦坠。

// private static File testOptDexFile;
private static HashSet<ShareDexDiffPatchInfo> classNDexInfo = new HashSet<>();
// classNDexInfo這個(gè)容器中的內(nèi)容是上面checkComplete中添加進(jìn)入的连躏。

public static boolean loadTinkerJars(final TinkerApplication application, String directory, String oatDir, Intent intentResult, boolean isSystemOTA, boolean isProtectedApp) {
        if (LOAD_DEX_LIST.isEmpty() && classNDexInfo.isEmpty()) {
            Log.w(TAG, "there is no dex to load");
            return true;
        }

        BaseDexClassLoader classLoader = (BaseDexClassLoader) TinkerDexLoader.class.getClassLoader();
        if (classLoader != null) {
            Log.i(TAG, "classloader: " + classLoader.toString());
        } else {
            Log.e(TAG, "classloader is null");
            ShareIntentUtil.setIntentReturnCode(intentResult, ShareConstants.ERROR_LOAD_PATCH_VERSION_DEX_CLASSLOADER_NULL);
            return false;
        }
        String dexPath = directory + "/" + DEX_PATH + "/";

        ArrayList<File> legalFiles = new ArrayList<>();

        for (ShareDexDiffPatchInfo info : LOAD_DEX_LIST) {
            //for dalvik, ignore art support dex
            if (isJustArtSupportDex(info)) {
                continue;
            }

            String path = dexPath + info.realName;
            File file = new File(path);
            // isTinkerLoadVerifyFlag這個(gè)標(biāo)志在項(xiàng)目的applictaion中配置,跳過(guò)可以加快速度
            if (application.isTinkerLoadVerifyFlag()) {
                long start = System.currentTimeMillis();
                String checkMd5 = getInfoMd5(info);
                if (!SharePatchFileUtil.verifyDexFileMd5(file, checkMd5)) {
                    //it is good to delete the mismatch file
                    ShareIntentUtil.setIntentReturnCode(intentResult, ShareConstants.ERROR_LOAD_PATCH_VERSION_DEX_MD5_MISMATCH);
                    intentResult.putExtra(ShareIntentUtil.INTENT_PATCH_MISMATCH_DEX_PATH,
                        file.getAbsolutePath());
                    return false;
                }
                Log.i(TAG, "verify dex file:" + file.getPath() + " md5, use time: " + (System.currentTimeMillis() - start));
            }
            // 將patch文件中的dex信息加入到legalFiles中
            legalFiles.add(file);
        }
        // verify merge classN.apk
        if (isVmArt && !classNDexInfo.isEmpty()) {
            File classNFile = new File(dexPath + ShareConstants.CLASS_N_APK_NAME);
            long start = System.currentTimeMillis();
            // 跳過(guò)對(duì)dex進(jìn)行md5校驗(yàn)的步驟贞滨,一般設(shè)置為跳過(guò)入热,可以加快速度
            legalFiles.add(classNFile);
        }
        File optimizeDir = new File(directory + "/" + oatDir);
        // 省略isSystemOTA為真的情況
        try {
            SystemClassLoaderAdder.installDexes(application, classLoader, optimizeDir, legalFiles, isProtectedApp);
        } catch (Throwable e) {
            Log.e(TAG, "install dexes failed");
            intentResult.putExtra(ShareIntentUtil.INTENT_PATCH_EXCEPTION, e);
            ShareIntentUtil.setIntentReturnCode(intentResult, ShareConstants.ERROR_LOAD_PATCH_VERSION_DEX_LOAD_EXCEPTION);
            return false;
        }
        return true;
    }

loadTinkerJars這個(gè)方法也是將patch中的信息處理加工,最后調(diào)用SystemClassLoaderAdder.installDexes方法開(kāi)始加載patch中的dex文件晓铆。代碼如下:

public static void installDexes(Application application, BaseDexClassLoader loader, File dexOptDir, List<File> files, boolean isProtectedApp)
       throws Throwable {
       Log.i(TAG, "installDexes dexOptDir: " + dexOptDir.getAbsolutePath() + ", dex size:" + files.size());

       if (!files.isEmpty()) {
           //將文件排序
           files = createSortedAdditionalPathEntries(files);
           ClassLoader classLoader = loader;
           if (Build.VERSION.SDK_INT >= 24 && !isProtectedApp) {
               classLoader = AndroidNClassLoader.inject(loader, application);
           }
           //because in dalvik, if inner class is not the same classloader with it wrapper class.
           //it won't fail at dex2opt
           if (Build.VERSION.SDK_INT >= 23) {
               V23.install(classLoader, files, dexOptDir);
           } else if (Build.VERSION.SDK_INT >= 19) {
               V19.install(classLoader, files, dexOptDir);
           } else if (Build.VERSION.SDK_INT >= 14) {
               V14.install(classLoader, files, dexOptDir);
           } else {
               V4.install(classLoader, files, dexOptDir);
           }
           //install done
           sPatchDexCount = files.size();
           Log.i(TAG, "after loaded classloader: " + classLoader + ", dex size:" + sPatchDexCount);

           if (!checkDexInstall(classLoader)) {
               //reset patch dex
               SystemClassLoaderAdder.uninstallPatchDex(classLoader);
               throw new TinkerRuntimeException(ShareConstants.CHECK_DEX_INSTALL_FAIL);
           }
       }
   }

經(jīng)過(guò)一系列的校驗(yàn)勺良,終于到了加載dex文件的地方了。如果是在24以上的機(jī)器骄噪,就調(diào)用AndroidNClassLoader的inject對(duì)classloader進(jìn)行處理尚困,為什么處理的原因在這里Android N混合編譯與對(duì)熱補(bǔ)丁影響解析。inject的過(guò)程有點(diǎn)復(fù)雜链蕊,我們先忽略事甜。接下來(lái)按照系統(tǒng)版本分為了四種方式加載補(bǔ)丁,這么做的原因的加載的過(guò)程有變化滔韵。各個(gè)歷史版本的代碼可以在這里來(lái)看AOSPXRef逻谦,非常方便。我們先分析下V23中的install過(guò)程陪蜻。代碼如下:

private static void install(ClassLoader loader, List<File> additionalClassPathEntries,
                                    File optimizedDirectory)
            throws IllegalArgumentException, IllegalAccessException,
            NoSuchFieldException, InvocationTargetException, NoSuchMethodException, IOException {
            /* The patched class loader is expected to be a descendant of
             * dalvik.system.BaseDexClassLoader. We modify its
             * dalvik.system.DexPathList pathList field to append additional DEX
             * file entries.
             */
            Field pathListField = ShareReflectUtil.findField(loader, "pathList");
            Object dexPathList = pathListField.get(loader);
            ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>();
            ShareReflectUtil.expandFieldArray(dexPathList, "dexElements", makePathElements(dexPathList,
                new ArrayList<File>(additionalClassPathEntries), optimizedDirectory,
                suppressedExceptions));
            if (suppressedExceptions.size() > 0) {
                for (IOException e : suppressedExceptions) {
                    Log.w(TAG, "Exception in makePathElement", e);
                    throw e;
                }

            }
        }

通過(guò)反射拿到BaseDexClassLoader中的pathList邦马,然后調(diào)用expandFieldArray更改這個(gè)成員變量中dexElements,用makePathElements組裝好的Elements數(shù)組替換原來(lái)的dexElements的值宴卖。類的加載都是由BaseDexClassLoader中的findclass來(lái)查找后加載滋将,而findclass就是從pathList這個(gè)數(shù)組中查找的。如果擴(kuò)展了原來(lái)BaseDexClassLoader的pathList變量症昏,從其中加入patch中的class耕渴,那就能替換原來(lái)出問(wèn)題的class。所以我們想怎樣讓pathList這個(gè)變量中加入我們前面patch文件中dex的class齿兔。DexPathList這個(gè)類中有個(gè) Element數(shù)組dexElements,我們可以通過(guò)反射的方式將Element數(shù)組更改,按照類加載器的原理分苇,加載到想要的類后就會(huì)直接返回添诉,因此如果我們只要將patch中dex插入到Element數(shù)組前面。之后加載特定類就會(huì)從patch中的dex加載修復(fù)后的類医寿,而不會(huì)從原有Element數(shù)組加載原來(lái)的類栏赴。插隊(duì)的操作是在expandFieldArray這個(gè)方法中完成,代碼如下:

/**
     * Replace the value of a field containing a non null array, by a new array containing the
     * elements of the original array plus the elements of extraElements.
     *
     * @param instance      the instance whose field is to be modified.
     * @param fieldName     the field to modify.
     * @param extraElements elements to append at the end of the array.
     */
    public static void expandFieldArray(Object instance, String fieldName, Object[] extraElements)
        throws NoSuchFieldException, IllegalArgumentException, IllegalAccessException {
        Field jlrField = findField(instance, fieldName);

        Object[] original = (Object[]) jlrField.get(instance);
        Object[] combined = (Object[]) Array.newInstance(original.getClass().getComponentType(), original.length + extraElements.length);

        // NOTE: changed to copy extraElements first, for patch load first

        System.arraycopy(extraElements, 0, combined, 0, extraElements.length);
        System.arraycopy(original, 0, combined, extraElements.length, original.length);

        jlrField.set(instance, combined);
    }

通過(guò)數(shù)組拷貝的方式將patch中dex生成的Element插入到數(shù)組的前面靖秩,而patch中的dex如何轉(zhuǎn)為成Element呢须眷?答案在makePathElements方法中,代碼如下:

 /**
 * A wrapper around
* {@code private static final dalvik.system.DexPathList#makePathElements}.
 */
private static Object[] makePathElements(
            Object dexPathList, ArrayList<File> files, File optimizedDirectory,
            ArrayList<IOException> suppressedExceptions)
            throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
            Method makePathElements;
            try {
                makePathElements = ShareReflectUtil.findMethod(dexPathList, "makePathElements", List.class, File.class,
                    List.class);
            } catch (NoSuchMethodException e) {
                Log.e(TAG, "NoSuchMethodException: makePathElements(List,File,List) failure");
                try {
                    makePathElements = ShareReflectUtil.findMethod(dexPathList, "makePathElements", ArrayList.class, File.class, ArrayList.class);
                } catch (NoSuchMethodException e1) {
                    Log.e(TAG, "NoSuchMethodException: makeDexElements(ArrayList,File,ArrayList) failure");
                    try {
                        Log.e(TAG, "NoSuchMethodException: try use v19 instead");
                        return V19.makeDexElements(dexPathList, files, optimizedDirectory, suppressedExceptions);
                    } catch (NoSuchMethodException e2) {
                        Log.e(TAG, "NoSuchMethodException: makeDexElements(List,File,List) failure");
                        throw e2;
                    }
                }
            }

            return (Object[]) makePathElements.invoke(dexPathList, files, optimizedDirectory, suppressedExceptions);
        }

這個(gè)方法也是通過(guò)反射調(diào)用DexPathList中的makePathElements方法沟突,最終將dex轉(zhuǎn)變?yōu)镋lelments花颗。下面看下makePathElements方法,代碼如下:

private static Element[] makeDexElements(List<File> files, File optimizedDirectory,
           List<IOException> suppressedExceptions, ClassLoader loader, boolean isTrusted) {
     Element[] elements = new Element[files.size()];
     int elementsPos = 0;
     /*
      * Open all files and load the (direct or contained) dex files up front.
      */
     for (File file : files) {
         if (file.isDirectory()) {
             // We support directories for looking up resources. Looking up resources in
             // directories is useful for running libcore tests.
             elements[elementsPos++] = new Element(file);
         } else if (file.isFile()) {
             String name = file.getName();

             DexFile dex = null;
             if (name.endsWith(DEX_SUFFIX)) {
                 // Raw dex file (not inside a zip/jar).
                 try {
                     dex = loadDexFile(file, optimizedDirectory, loader, elements);
                     if (dex != null) {
                         elements[elementsPos++] = new Element(dex, null);
                     }
                 } catch (IOException suppressed) {
                     System.logE("Unable to load dex file: " + file, suppressed);
                     suppressedExceptions.add(suppressed);
                 }
             } else {
                 try {
                     dex = loadDexFile(file, optimizedDirectory, loader, elements);
                 } catch (IOException suppressed) {
                     /*
                      * IOException might get thrown "legitimately" by the DexFile constructor if
                      * the zip file turns out to be resource-only (that is, no classes.dex file
                      * in it).
                      * Let dex == null and hang on to the exception to add to the tea-leaves for
                      * when findClass returns null.
                      */
                     suppressedExceptions.add(suppressed);
                 }

                 if (dex == null) {
                     elements[elementsPos++] = new Element(file);
                 } else {
                     elements[elementsPos++] = new Element(dex, file);
                 }
             }
             if (dex != null && isTrusted) {
               dex.setTrusted();
             }
         } else {
             System.logW("ClassLoader referenced unknown path: " + file);
         }
     }
     if (elementsPos != elements.length) {
         elements = Arrays.copyOf(elements, elementsPos);
     }
     return elements;
 }

在這個(gè)方法中通過(guò)loadDexFile 將dex轉(zhuǎn)化為Elelments惠拭。然后將Element返回扩劝。至此我們分析了dex的校驗(yàn),加載职辅,以及最后的插入到dexPathList中棒呛。其他版本最后的install方法也比較類似,其他流程的講解參考Android 熱修復(fù)方案Tinker(三) Dex補(bǔ)丁加載.這篇文章講的比較詳細(xì)域携,可以結(jié)合不同版本的代碼學(xué)習(xí)下簇秒。

參考鏈接

感謝微信的開(kāi)源,讓tinker成為了小廠的基礎(chǔ)設(shè)置秀鞭,讓很多應(yīng)用開(kāi)發(fā)者擺脫了熱更的諸多麻煩趋观。

感謝先行者的詳細(xì)分析和無(wú)私分享。功力尚淺气筋,請(qǐng)不吝指教拆内。

Android 熱修復(fù)方案Tinker(三) Dex補(bǔ)丁加載

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市宠默,隨后出現(xiàn)的幾起案子麸恍,更是在濱河造成了極大的恐慌,老刑警劉巖搀矫,帶你破解...
    沈念sama閱讀 211,639評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件抹沪,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡瓤球,警方通過(guò)查閱死者的電腦和手機(jī)融欧,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,277評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)卦羡,“玉大人噪馏,你說(shuō)我怎么就攤上這事麦到。” “怎么了欠肾?”我有些...
    開(kāi)封第一講書(shū)人閱讀 157,221評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵瓶颠,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我刺桃,道長(zhǎng)粹淋,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,474評(píng)論 1 283
  • 正文 為了忘掉前任瑟慈,我火速辦了婚禮桃移,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘葛碧。我一直安慰自己借杰,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,570評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布吹埠。 她就那樣靜靜地躺著第步,像睡著了一般。 火紅的嫁衣襯著肌膚如雪缘琅。 梳的紋絲不亂的頭發(fā)上粘都,一...
    開(kāi)封第一講書(shū)人閱讀 49,816評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音刷袍,去河邊找鬼翩隧。 笑死,一個(gè)胖子當(dāng)著我的面吹牛呻纹,可吹牛的內(nèi)容都是我干的堆生。 我是一名探鬼主播,決...
    沈念sama閱讀 38,957評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼雷酪,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼淑仆!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起哥力,我...
    開(kāi)封第一講書(shū)人閱讀 37,718評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤蔗怠,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后吩跋,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體寞射,經(jīng)...
    沈念sama閱讀 44,176評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,511評(píng)論 2 327
  • 正文 我和宋清朗相戀三年锌钮,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了桥温。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,646評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡梁丘,死狀恐怖侵浸,靈堂內(nèi)的尸體忽然破棺而出旺韭,到底是詐尸還是另有隱情,我是刑警寧澤掏觉,帶...
    沈念sama閱讀 34,322評(píng)論 4 330
  • 正文 年R本政府宣布茂翔,位于F島的核電站,受9級(jí)特大地震影響履腋,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜惭嚣,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,934評(píng)論 3 313
  • 文/蒙蒙 一遵湖、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧晚吞,春花似錦延旧、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,755評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至捌蚊,卻和暖如春集畅,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背缅糟。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,987評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工挺智, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人窗宦。 一個(gè)月前我還...
    沈念sama閱讀 46,358評(píng)論 2 360
  • 正文 我出身青樓赦颇,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親赴涵。 傳聞我的和親對(duì)象是個(gè)殘疾皇子媒怯,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,514評(píng)論 2 348