本系列 Tinker 源码解析基于 Tinker v1.9.12
加载dex补丁流程
TinkerDexLoader.loadTinkerJars
判断一下 dexList 和 classLoader
1 | if (loadDexList.isEmpty() && classNDexInfo.isEmpty()) { |
如果 TinkerLoadVerifyFlag 为 true 的话,会对每个 dex 进行 md5 校验
1 | String dexPath = directory + "/" + DEX_PATH + "/"; |
如果是 art 虚拟机并且是 Android N 及以上的环境,会另外加上 tinker_classN.apk
1 | // verify merge classN.apk |
如果用户是ART虚拟机并且做了OTA升级,那么在加载dex补丁的时候就会先把最近一次的补丁全部DexFile.loadDex一遍.这么做的原因是有些场景做了OTA后,oat的规则可能发生变化,在这种情况下去加载上个系统版本oat过的dex就会出现问题.
1 | File optimizeDir = new File(directory + "/" + oatDir); |
加载dex
1 | try { |
SystemClassLoaderAdder.installDexes
1 | public static void installDexes(Application application, PathClassLoader loader, File dexOptDir, List<File> files) |
可以看到,在加载 dex 的时候,分别分成了四个版本:
- v4
- v14
- v19
- v23
其中如果是 SDK 24 及以上,需要改造一下 classloder
针对每个版本不同的源码,进行 dex 插入
我们就来看其中一个版本 v19
1 | private static final class V19 { |
首先反射拿到反射得到 PathClassLoader 中的 pathList 对象,再将补丁文件通过反射调用makeDexElements 得到补丁文件的 Element[] ,再将补丁包的 Element[] 数组插入到 dexElements 中
另外,需要对 Android 7.0 及以上单独处理一下,具体看 Android N混合编译与对热补丁影响解析
加载补丁操作做好之后,最后还要检查一下,如果没加载成功就会执行卸载:
1 | if (!checkDexInstall(classLoader)) { |
具体验证补丁是否加载成功的方法就是判断 TinkerTestDexLoad.isPatch 的值。
在没有补丁加载的情况下都是返回 false 的, 在补丁中修改 isPatch 属性为 true 。所以只要反射拿到isPatch 的属性为 true 就说明补丁已经成功加载进来了。否则就调用 SystemClassLoaderAdder.uninstallPatchDex 执行卸载
1 | public static void uninstallPatchDex(ClassLoader classLoader) throws Throwable { |
卸载补丁可以说是加载补丁的逆向操作,具体操作可以分成 v4 和 v14 两个版本
具体的内容就是把 dexElements 中的头部 element 去除了。
到这,dex 补丁加载的流程结束了。