Tinker源码分析(三):加载dex补丁流程

栏目: 编程工具 · 发布时间: 6年前

内容简介:判断一下 dexList 和 classLoader如果 TinkerLoadVerifyFlag 为 true 的话,会对每个 dex 进行 md5 校验如果是 art 虚拟机并且是 Android N 及以上的环境,会另外加上 tinker_classN.apk

判断一下 dexList 和 classLoader

if (loadDexList.isEmpty() && classNDexInfo.isEmpty()) {
    Log.w(TAG, "there is no dex to load");
    return true;
}

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

如果 TinkerLoadVerifyFlag 为 true 的话,会对每个 dex 进行 md5 校验

String dexPath = directory + "/" + DEX_PATH + "/";

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

for (ShareDexDiffPatchInfo info : loadDexList) {
    //for dalvik, ignore art support dex
    // 对于 dalvik 虚拟机,忽略 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);
        // 校验dex文件的 md5 值
        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));
    }
    // dex文件都通过的话,加入到 legalFiles 集合中 
    legalFiles.add(file);
}

如果是 art 虚拟机并且是 Android N 及以上的环境,会另外加上 tinker_classN.apk

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

如果用户是ART虚拟机并且做了OTA升级,那么在加载dex补丁的时候就会先把最近一次的补丁全部DexFile.loadDex一遍.这么做的原因是有些场景做了OTA后,oat的规则可能发生变化,在这种情况下去加载上个系统版本oat过的dex就会出现问题.

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

            // 对 dex 文件作 odex 处理
            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;
            }
        }

加载dex

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;

SystemClassLoaderAdder.installDexes

public static void installDexes(Application application, PathClassLoader loader, File dexOptDir, List<File> files)
    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 && !checkIsProtectedApp(files)) {
            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);
        }
    }
}

可以看到,在加载 dex 的时候,分别分成了四个版本:

  • v4
  • v14
  • v19
  • v23

其中如果是 SDK 24 及以上,需要改造一下 classloder

针对每个版本不同的源码,进行 dex 插入

我们就来看其中一个版本 v19

private static final class V19 {

    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", makeDexElements(dexPathList,
            new ArrayList<File>(additionalClassPathEntries), optimizedDirectory,
            suppressedExceptions));
        if (suppressedExceptions.size() > 0) {
            for (IOException e : suppressedExceptions) {
                Log.w(TAG, "Exception in makeDexElement", e);
                throw e;
            }
        }
    }

    /**
     * A wrapper around
     * {@code private static final dalvik.system.DexPathList#makeDexElements}.
     */
    private static Object[] makeDexElements(
        Object dexPathList, ArrayList<File> files, File optimizedDirectory,
        ArrayList<IOException> suppressedExceptions)
        throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {

        Method makeDexElements = null;
        try {
            makeDexElements = ShareReflectUtil.findMethod(dexPathList, "makeDexElements", ArrayList.class, File.class,
                ArrayList.class);
        } catch (NoSuchMethodException e) {
            Log.e(TAG, "NoSuchMethodException: makeDexElements(ArrayList,File,ArrayList) failure");
            // 对不同的 rom 做兼容,有的 rom 的 makeDexElements 方法参数类型是 List
            try {
                makeDexElements = ShareReflectUtil.findMethod(dexPathList, "makeDexElements", List.class, File.class, List.class);
            } catch (NoSuchMethodException e1) {
                Log.e(TAG, "NoSuchMethodException: makeDexElements(List,File,List) failure");
                throw e1;
            }
        }

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

首先反射拿到反射得到 PathClassLoader 中的 pathList 对象,再将补丁文件通过反射调用makeDexElements 得到补丁文件的 Element[] ,再将补丁包的 Element[] 数组插入到 dexElements 中

另外,需要对 Android 7.0 及以上单独处理一下,具体看 Android N混合编译与对热补丁影响解析

加载补丁操作做好之后,最后还要检查一下,如果没加载成功就会执行卸载:

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

具体验证补丁是否加载成功的方法就是判断 TinkerTestDexLoad.isPatch 的值。

在没有补丁加载的情况下都是返回 false 的, 在补丁中修改 isPatch 属性为 true 。所以只要反射拿到isPatch 的属性为 true 就说明补丁已经成功加载进来了。否则就调用 SystemClassLoaderAdder.uninstallPatchDex 执行卸载

public static void uninstallPatchDex(ClassLoader classLoader) throws Throwable {
    if (sPatchDexCount <= 0) {
        return;
    }
    if (Build.VERSION.SDK_INT >= 14) {
        Field pathListField = ShareReflectUtil.findField(classLoader, "pathList");
        Object dexPathList = pathListField.get(classLoader);
        ShareReflectUtil.reduceFieldArray(dexPathList, "dexElements", sPatchDexCount);
    } else {
        ShareReflectUtil.reduceFieldArray(classLoader, "mPaths", sPatchDexCount);
        ShareReflectUtil.reduceFieldArray(classLoader, "mFiles", sPatchDexCount);
        ShareReflectUtil.reduceFieldArray(classLoader, "mZips", sPatchDexCount);
        try {
            ShareReflectUtil.reduceFieldArray(classLoader, "mDexs", sPatchDexCount);
        } catch (Exception e) {
        }
    }
}

卸载补丁可以说是加载补丁的逆向操作,具体操作可以分成 v4 和 v14 两个版本

具体的内容就是把 dexElements 中的头部 element 去除了。


以上所述就是小编给大家介绍的《Tinker源码分析(三):加载dex补丁流程》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Haskell

Haskell

Simon Thompson / Addison-Wesley / 1999-3-16 / GBP 40.99

The second edition of Haskell: The Craft of Functional Programming is essential reading for beginners to functional programming and newcomers to the Haskell programming language. The emphasis is on th......一起来看看 《Haskell》 这本书的介绍吧!

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

html转js在线工具
html转js在线工具

html转js在线工具

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具