当前位置: 首页 > 工具软件 > fast dex > 使用案例 >

tinker热修复——dex补丁加载过程

樊宏义
2023-12-01

Android使用hook来做热修复的原理,都需要用到类的加载器来dex,将出bug的dex替换掉,来达到热修复的目的,至于热修复的原理,在《你值得知道的Android 热修复,以及热修复原理》这篇文章中已经做过详细的介绍,那么tinker的dex补丁加载过程是否也是使用同样的原理呢?本文就是要为读者解剖tinker的dex补丁加载过程。

如果读者没有将tinker接入过项目的,可以阅读《Android tinker热修复——实战接入项目》这篇文章,该文章带领读者将tinker接入到自己的项目,实现热修复,并且本文也是从《Android tinker热修复——实战接入项目》这篇文章的基础上做一个深入的探究,因此建议读者阅读下我之前写过的热修复系列的文章:

《你值得知道的Android 热修复,以及热修复原理》

《Android tinker热修复——从运行demo开始》

《Android tinker热修复——实战接入项目》

《tinker热修复——补丁加载合成》

《tinker热修复——资源补丁加载过程》

《tinker热修复——install过程》

同时希望读者关注我的专题Android热修复和插件化,我将会为读者更新一些列有关热修复和插件化的技术文章,也希望大家来投稿,一起交流这系列的技术。

TinkerApplication

在使用tinker的时候,首先需要在AndroidManifest.xml的application标签指定自定义的application,而这个application需要继承TinkerApplication。那么首先就要看下TinkerApplication的attachBaseContext方法,因为application的方法执行顺序如下:

所以首先会调用TinkerApplication的attachBaseContext。

setDefaultUncaughtExceptionHandler方法是用来收集crash的,我们这里不分析crash,主要分析dex补丁加载过程,所以我们继续往下看,执行的是该类的内部方法onBaseContextAttached。

首先调用loadTinker:

这里的loaderClassName也就是从MyApplication的构造方法super过来的com.tencent.tinker.loader.TinkerLoader

因此loadTinker方法中最终通过反射调用TinkerLoader类的tryLoad方法。而在onBaseContextAttached方法中调用的ensureDelegate方法,其实就是初始化MyTestApplicationLike类,然后调用其onBaseContextAttached方法来进行tinker的install过程,初始化tinker的一些配置。

关于该过程,可以参考上一篇文章《tinker热修复——install过程》,那么热修复过程显然是在TinkerLoader的tryLoad中进行的。

tryLoad方法的注释说明,只能在主线程中处理补丁版本的更改,在tryLoad方法中直接调用tryLoadPatchFilesInternal方法完成补丁的热修复。

    private void tryLoadPatchFilesInternal(TinkerApplication app, Intent resultIntent) {
        final int tinkerFlag = app.getTinkerFlags();

        //判断是否开启热修复
        if (!ShareTinkerInternals.isTinkerEnabled(tinkerFlag)) {
            Log.w(TAG, "tryLoadPatchFiles: tinker is disable, just return");
            ShareIntentUtil.setIntentReturnCode(resultIntent, ShareConstants.ERROR_LOAD_DISABLE);
            return;
        }
        //是否在patch进程
        if (ShareTinkerInternals.isInPatchProcess(app)) {
            Log.w(TAG, "tryLoadPatchFiles: we don't load patch with :patch process itself, just return");
            ShareIntentUtil.setIntentReturnCode(resultIntent, ShareConstants.ERROR_LOAD_DISABLE);
            return;

        }
        //tinker,补丁的文件目录是否为null
        File patchDirectoryFile = SharePatchFileUtil.getPatchDirectory(app);
        if (patchDirectoryFile == null) {
            Log.w(TAG, "tryLoadPatchFiles:getPatchDirectory == null");
            //treat as not exist
            ShareIntentUtil.setIntentReturnCode(resultIntent, ShareConstants.ERROR_LOAD_PATCH_DIRECTORY_NOT_EXIST);
            return;
        }
        String patchDirectoryPath = patchDirectoryFile.getAbsolutePath();

        //check patch directory whether exist,补丁的路径是否存在
        if (!patchDirectoryFile.exists()) {
            Log.w(TAG, "tryLoadPatchFiles:patch dir not exist:" + patchDirectoryPath);
            ShareIntentUtil.setIntentReturnCode(resultIntent, ShareConstants.ERROR_LOAD_PATCH_DIRECTORY_NOT_EXIST);
            return;
        }

        //tinker/patch.info
        File patchInfoFile = SharePatchFileUtil.getPatchInfoFile(patchDirectoryPath);

        //check patch info file whether exist 检查补丁信息的文件是否存在
        if (!patchInfoFile.exists()) {
            Log.w(TAG, "tryLoadPatchFiles:patch info not exist:" + patchInfoFile.getAbsolutePath());
            ShareIntentUtil.setIntentReturnCode(resultIntent, ShareConstants.ERROR_LOAD_PATCH_INFO_NOT_EXIST);
            return;
        }
        //old = 641e634c5b8f1649c75caf73794acbdf
        //new = 2c150d8560334966952678930ba67fa8
        File patchInfoLockFile = SharePatchFileUtil.getPatchInfoLockFile(patchDirectoryPath);

        //补丁信息
        patchInfo = SharePatchInfo.readAndCheckPropertyWithLock(patchInfoFile, patchInfoLockFile);
        if (patchInfo == null) {
            ShareIntentUtil.setIntentReturnCode(resultIntent, ShareConstants.ERROR_LOAD_PATCH_INFO_CORRUPTED);
            return;
        }

        String oldVersion = patchInfo.oldVersion;//旧版本
        String newVersion = patchInfo.newVersion;//新版本
        String oatDex = patchInfo.oatDir;

        if (oldVersion == null || newVersion == null || oatDex == null) {
            //it is nice to clean patch
            Log.w(TAG, "tryLoadPatchFiles:onPatchInfoCorrupted");
            ShareIntentUtil.setIntentReturnCode(resultIntent, ShareConstants.ERROR_LOAD_PATCH_INFO_CORRUPTED);
            return;
        }

        //将新旧版本的put到intent中去
        resultIntent.putExtra(ShareIntentUtil.INTENT_PATCH_OLD_VERSION, oldVersion);
        resultIntent.putExtra(ShareIntentUtil.INTENT_PATCH_NEW_VERSION, newVersion);

        boolean mainProcess = ShareTinkerInternals.isInMainProcess(app);
        boolean versionChanged = !(oldVersion.equals(newVersion));
        boolean oatModeChanged = oatDex.equals(ShareConstants.CHANING_DEX_OPTIMIZE_PATH) && mainProcess;
        oatDex = ShareTinkerInternals.getCurrentOatMode(app, oatDex);
        resultIntent.putExtra(ShareIntentUtil.INTENT_PATCH_OAT_DIR, oatDex);

        String version = oldVersion;

        if (versionChanged && mainProcess) {
            version = newVersion;
        }
        if (ShareTinkerInternals.isNullOrNil(version)) {
            Log.w(TAG, "tryLoadPatchFiles:version is blank, wait main process to restart");
            ShareIntentUtil.setIntentReturnCode(resultIntent, ShareConstants.ERROR_LOAD_PATCH_INFO_BLANK);
            return;
        }

        //如果获取不到补丁,那么就需要删掉补丁的信息
        //patch-641e634c
        String patchName = SharePatchFileUtil.getPatchVersionDirectory(version);
        if (patchName == null) {
            Log.w(TAG, "tryLoadPatchFiles:patchName is null");
            //we may delete patch info file
            ShareIntentUtil.setIntentReturnCode(resultIntent, ShareConstants.ERROR_LOAD_PATCH_VERSION_DIRECTORY_NOT_EXIST);
            return;
        }
        //tinker/patch.info/patch-641e634c
        String patchVersionDirectory = patchDirectoryPath + "/" + patchName;

        File patchVersionDirectoryFile = new File(patchVersionDirectory);

        //检查补丁是否存在
        if (!patchVersionDirectoryFile.exists()) {
            Log.w(TAG, "tryLoadPatchFiles:onPatchVersionDirectoryNotFound");
            //we may delete patch info file
            ShareIntentUtil.setIntentReturnCode(resultIntent, ShareConstants.ERROR_LOAD_PATCH_VERSION_DIRECTORY_NOT_EXIST);
            return;
        }

        //tinker/patch.info/patch-641e634c/patch-641e634c.apk
        File patchVersionFile = new File(patchVersionDirectoryFile.getAbsolutePath(), SharePatchFileUtil.getPatchVersionFile(version));

        //检查补丁文件是否存在
        if (!SharePatchFileUtil.isLegalFile(patchVersionFile)) {
            Log.w(TAG, "tryLoadPatchFiles:onPatchVersionFileNotFound");
            //we may delete patch info file
            ShareIntentUtil.setIntentReturnCode(resultIntent, ShareConstants.ERROR_LOAD_PATCH_VERSION_FILE_NOT_EXIST);
            return;
        }

        ShareSecurityCheck securityCheck = new ShareSecurityCheck(app);
        int returnCode = ShareTinkerInternals.checkTinkerPackage(app, tinkerFlag, patchVersionFile, securityCheck);
        if (returnCode != ShareConstants.ERROR_PACKAGE_CHECK_OK) {
            Log.w(TAG, "tryLoadPatchFiles:checkTinkerPackage");
            resultIntent.putExtra(ShareIntentUtil.INTENT_PATCH_PACKAGE_PATCH_CHECK, returnCode);
            ShareIntentUtil.setIntentReturnCode(resultIntent, ShareConstants.ERROR_LOAD_PATCH_PACKAGE_CHECK_FAIL);
            return;
        }

        resultIntent.putExtra(ShareIntentUtil.INTENT_PATCH_PACKAGE_CONFIG, securityCheck.getPackagePropertiesIfPresent());

        final boolean isEnabledForDex = ShareTinkerInternals.isTinkerEnabledForDex(tinkerFlag);

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

        //检查lib库
        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 && versionChanged)
             || oatModeChanged) {
            patchInfo.oldVersion = version;
            patchInfo.oatDir = oatDex;

            //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;
            }
            if (oatModeChanged) {
                // 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 (isEnabledForDex) {
            boolean loadTinkerJars = TinkerDexLoader.loadTinkerJars(app, patchVersionDirectory, oatDex, resultIntent, isSystemOTA);

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

        //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 && isEnabledForResource) {
            ComponentHotplug.install(app, securityCheck);
        }

        // kill all other process if oat mode change
        if (oatModeChanged) {
            ShareTinkerInternals.killAllOtherProcess(app);
            Log.i(TAG, "tryLoadPatchFiles:oatModeChanged, try to kill all other process");
        }
        //all is ok!
        ShareIntentUtil.setIntentReturnCode(resultIntent, ShareConstants.ERROR_LOAD_OK);
        Log.i(TAG, "tryLoadPatchFiles: load end, ok!");
        return;
    }
复制代码

tryLoadPatchFilesInternal方法,代码量很大,但是逻辑还是非常的清晰的,首先要经过一层层的检查,包括检查是否开启热修复、是否在patch进程和补丁的文件目录是否为null等等,具体可以看上面代码的注释。经过这么多的判断之后,在后面我们终于找到加载补丁的jar方法了,那就是TinkerDexLoader的loadTinkerJars方法。而在《你值得知道的Android 热修复,以及热修复原理》中介绍过类加载器可以加载dex、jar、zip和apk文件

而在调用loadTinkerJars方法之前调用了TinkerDexLoader的checkComplete方法。

/**
     * all the dex files in meta file exist?
     * fast check, only check whether exist
     *
     * @return boolean
     */
    public static boolean checkComplete(String directory, ShareSecurityCheck securityCheck, String oatDir, Intent intentResult) {
        //DEX_MEAT_FILE:assets/dex_meta.txt
        String meta = securityCheck.getMetaContentMap().get(DEX_MEAT_FILE);
        //not found dex
        if (meta == null) {
            return true;
        }
        //清空loadDexList
        loadDexList.clear();
        //清空classNDexInfo
        classNDexInfo.clear();

        ArrayList<ShareDexDiffPatchInfo> allDexInfo = new ArrayList<>();
        //读取dex_meta.txt文件信息
        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));
                loadDexList.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, "");
        }
        //tinker/patch.info/patch-641e634c/dex
        String dexDirectory = directory + "/" + DEX_PATH + "/";

        File dexDir = new File(dexDirectory);

        if (!dexDir.exists() || !dexDir.isDirectory()) {
            ShareIntentUtil.setIntentReturnCode(intentResult, ShareConstants.ERROR_LOAD_PATCH_VERSION_DEX_DIRECTORY_NOT_EXIST);
            return false;
        }
        String optimizeDexDirectory = directory + "/" + oatDir + "/";
        File optimizeDexDirectoryFile = new File(optimizeDexDirectory);

        //fast check whether there is any dex files missing
        for (String name : dexes.keySet()) {
            File dexFile = new File(dexDirectory + name);

            if (!SharePatchFileUtil.isLegalFile(dexFile)) {
                intentResult.putExtra(ShareIntentUtil.INTENT_PATCH_MISSING_DEX_PATH, dexFile.getAbsolutePath());
                ShareIntentUtil.setIntentReturnCode(intentResult, ShareConstants.ERROR_LOAD_PATCH_VERSION_DEX_FILE_NOT_EXIST);
                return false;
            }
            //check dex opt whether complete also
            File dexOptFile = new File(SharePatchFileUtil.optimizedPathFor(dexFile, optimizeDexDirectoryFile));
            if (!SharePatchFileUtil.isLegalFile(dexOptFile)) {
                intentResult.putExtra(ShareIntentUtil.INTENT_PATCH_MISSING_DEX_PATH, dexOptFile.getAbsolutePath());
                ShareIntentUtil.setIntentReturnCode(intentResult, ShareConstants.ERROR_LOAD_PATCH_VERSION_DEX_OPT_FILE_NOT_EXIST);
                return false;
            }
//            // find test dex
//            if (dexOptFile.getName().startsWith(ShareConstants.TEST_DEX_NAME)) {
//                testOptDexFile = dexOptFile;
//            }
        }

        //if is ok, add to result intent
        intentResult.putExtra(ShareIntentUtil.INTENT_PATCH_DEXES_PATH, dexes);
        return true;
    }
复制代码

就像注释说明的那样,checkComplete只是检查在assets/dex_meta.txt文件中dex文件,而且快速的检查,只是检查是否存在,并将解析得到的信息,存储到一个ShareDexDiffPatchInfo的对象,然后将对象存放到loadDexList或者classNDexInfo中。

接下来我们看loadTinkerJars方法的实现

/**
     * Load tinker JARs and add them to
     * the Application ClassLoader.
     *
     * @param application The application.
     */
    @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
    public static boolean loadTinkerJars(final TinkerApplication application, String directory, String oatDir, Intent intentResult, boolean isSystemOTA) {
        //通过TinkerDexLoader的checkComplete方法,得到loadDexList和classNDexInfo
        if (loadDexList.isEmpty() && classNDexInfo.isEmpty()) {
            Log.w(TAG, "there is no dex to load");
            return true;
        }

        //the Application ClassLoader
        PathClassLoader classLoader = (PathClassLoader) 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 : loadDexList) {
            //for dalvik, ignore art support dex
            if (isJustArtSupportDex(info)) {
                continue;
            }

            String path = dexPath + info.realName;
            File file = new File(path);

            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));
            }
            //将文件加入队列
            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();

            if (application.isTinkerLoadVerifyFlag()) {
                for (ShareDexDiffPatchInfo info : classNDexInfo) {
                    if (!SharePatchFileUtil.verifyDexFileMd5(classNFile, info.rawName, info.destMd5InArt)) {
                        ShareIntentUtil.setIntentReturnCode(intentResult, ShareConstants.ERROR_LOAD_PATCH_VERSION_DEX_MD5_MISMATCH);
                        intentResult.putExtra(ShareIntentUtil.INTENT_PATCH_MISMATCH_DEX_PATH,
                            classNFile.getAbsolutePath());
                        return false;
                    }
                }
            }
            Log.i(TAG, "verify dex file:" + classNFile.getPath() + " md5, use time: " + (System.currentTimeMillis() - start));
            //将文件加入队列
            legalFiles.add(classNFile);
        }
        //优化补丁的目录
        File optimizeDir = new File(directory + "/" + oatDir);

        if (isSystemOTA) {
            final boolean[] parallelOTAResult = {true};
            final Throwable[] parallelOTAThrowable = new Throwable[1];
            String targetISA;
            try {
                targetISA = ShareTinkerInternals.getCurrentInstructionSet();
            } catch (Throwable throwable) {
                Log.i(TAG, "getCurrentInstructionSet fail:" + throwable);
//                try {
//                    targetISA = ShareOatUtil.getOatFileInstructionSet(testOptDexFile);
//                } catch (Throwable throwable) {
                // don't ota on the front
                deleteOutOfDateOATFile(directory);

                intentResult.putExtra(ShareIntentUtil.INTENT_PATCH_INTERPRET_EXCEPTION, throwable);
                ShareIntentUtil.setIntentReturnCode(intentResult, ShareConstants.ERROR_LOAD_PATCH_GET_OTA_INSTRUCTION_SET_EXCEPTION);
                return false;
//                }
            }

            deleteOutOfDateOATFile(directory);

            Log.w(TAG, "systemOTA, try parallel oat dexes, targetISA:" + targetISA);
            // change dir
            optimizeDir = new File(directory + "/" + INTERPRET_DEX_OPTIMIZE_PATH);

            TinkerDexOptimizer.optimizeAll(
                legalFiles, optimizeDir, true, targetISA,
                new TinkerDexOptimizer.ResultCallback() {
                    long start;

                    @Override
                    public void onStart(File dexFile, File optimizedDir) {
                        start = System.currentTimeMillis();
                        Log.i(TAG, "start to optimize dex:" + dexFile.getPath());
                    }

                    @Override
                    public void onSuccess(File dexFile, File optimizedDir, File optimizedFile) {
                        // Do nothing.
                        Log.i(TAG, "success to optimize dex " + dexFile.getPath() + ", use time " + (System.currentTimeMillis() - start));
                    }

                    @Override
                    public void onFailed(File dexFile, File optimizedDir, Throwable thr) {
                        parallelOTAResult[0] = false;
                        parallelOTAThrowable[0] = thr;
                        Log.i(TAG, "fail to optimize dex " + dexFile.getPath() + ", use time " + (System.currentTimeMillis() - start));
                    }
                }
            );


            if (!parallelOTAResult[0]) {
                Log.e(TAG, "parallel oat dexes failed");
                intentResult.putExtra(ShareIntentUtil.INTENT_PATCH_INTERPRET_EXCEPTION, parallelOTAThrowable[0]);
                ShareIntentUtil.setIntentReturnCode(intentResult, ShareConstants.ERROR_LOAD_PATCH_OTA_INTERPRET_ONLY_EXCEPTION);
                return false;
            }
        }
        try {
            SystemClassLoaderAdder.installDexes(application, classLoader, optimizeDir, legalFiles);
        } catch (Throwable e) {
            Log.e(TAG, "install dexes failed");
//            e.printStackTrace();
            intentResult.putExtra(ShareIntentUtil.INTENT_PATCH_EXCEPTION, e);
            ShareIntentUtil.setIntentReturnCode(intentResult, ShareConstants.ERROR_LOAD_PATCH_VERSION_DEX_LOAD_EXCEPTION);
            return false;
        }

        return true;
    }
复制代码

首先判断loadDexList和classNDexInfo有没有元素,在TinkerDexLoader的checkComplete方法,里面已经添加完成了元素。然后通过TinkerDexLoader的class.getClassLoader()方法拿到Application的ClassLoader,接下来将补丁文件添加到legalFiles队列,最重要的就是通过SystemClassLoaderAdder的installDexes方法安装dex,也就是通过该方法来完成热修复。

这里会根据不同的版本调用不同版本的install方法,这里看Build.VERSION.SDK_INT >= 19这个版本的install方法。

首先通过调用ShareReflectUtil的findField方法,也就是通过反射来获取得到PathClassLoader的pathList属性。

/**
     * Locates a given field anywhere in the class inheritance hierarchy.
     *
     * @param instance an object to search the field into.
     * @param name     field name
     * @return a field object
     * @throws NoSuchFieldException if the field cannot be located
     */
    public static Field findField(Object instance, String name) throws NoSuchFieldException {
        for (Class<?> clazz = instance.getClass(); clazz != null; clazz = clazz.getSuperclass()) {
            try {
                Field field = clazz.getDeclaredField(name);

                if (!field.isAccessible()) {
                    field.setAccessible(true);
                }

                return field;
            } catch (NoSuchFieldException e) {
                // ignore and search next
            }
        }

        throw new NoSuchFieldException("Field " + name + " not found in " + instance.getClass());
    }
复制代码

接着V19的install方法,得到PathClassLoader的pathList属性之后,通过pathListField.get(loader)得到DexPathList对象,接着调用ShareReflectUtil的expandFieldArray方法。

    /**
     * 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);
    }
复制代码

方法内,首先调用findField方法得到pathListField的dexElements数组。

/**
     * Locates a given field anywhere in the class inheritance hierarchy.
     *
     * @param instance an object to search the field into.
     * @param name     field name
     * @return a field object
     * @throws NoSuchFieldException if the field cannot be located
     */
    public static Field findField(Object instance, String name) throws NoSuchFieldException {
        for (Class<?> clazz = instance.getClass(); clazz != null; clazz = clazz.getSuperclass()) {
            try {
                Field field = clazz.getDeclaredField(name);

                if (!field.isAccessible()) {
                    field.setAccessible(true);
                }

                return field;
            } catch (NoSuchFieldException e) {
                // ignore and search next
            }
        }

        throw new NoSuchFieldException("Field " + name + " not found in " + instance.getClass());
    }
复制代码

《你值得知道的Android 热修复,以及热修复原理》这篇文章中已经介绍过了通过反射得到dexElements这些原理了,毫无疑问,tinker也是使用了相同的原理来进行热修复。

在expandFieldArray方法中,new了一个数据类型为object的数组combined,其长度是:原本dexElements的长度+通过dexPathList对象的makeDexElements方法创建的长度,通过System.arraycopy将补丁插入dex插入到combined最前面的位置,也就是0的位置,然后通过jlrField.set(instance, combined),用combined将就的dexElements替换掉。替换掉之后,每次加载dex,都是从新的dexElements开始查找,因为修复好的dex在最前面,一旦找到新的dex,就不会再从dexElements往下查找旧dex,这样子就达到了热修复的目的。

通过以上的分析,希望能够提升大家对tinker源码和热修复原理的理解。

 类似资料: