热修复Tinker 原理解析之Dex更新

本文深入解析Android热修复库Tinker的Dex更新流程,包括补丁生成、全量Dex合成及加载过程。详细介绍了DexDiffDecoder如何生成补丁文件,以及Tinker在接收到补丁后如何合成并加载全量Dex,帮助读者理解Tinker的Dex热更新机制。
摘要由CSDN通过智能技术生成

前言:在之前已经梳理了微信的热修复Tinker的接入使用流程,这么牛逼的东西勾起了我的兴趣,因此走上了探究其实现原理的道路。Tinker支持Dex、资源文件、so文件的热更新,此次分析过程也将一步步的从这三个方面对Tinker进行源码解析,跟着我的梳理希望你也可以有所收获。

Android tinker接入使用

tinker之资源更新详解

tinker之so更新详解

在分析之前先copy出Tinker的原理图和流程图:

微信Tinkeråçå¾

Tinkeræµç¨å¾

通过今天的文章咱们先把Dex更新的原理给搞明白了再说。

一、生成补丁流程

当在命令行里面调用tinkerPatchRelease任务时会调用com.tencent.tinker.build.patch.Runner.tinkerPatch()进行生成补丁生成过程:

//gen patch
ApkDecoder decoder = new ApkDecoder(config);
decoder.onAllPatchesStart();
decoder.patch(config.mOldApkFile, config.mNewApkFile);
decoder.onAllPatchesEnd();
//gen meta file and version file
PatchInfo info = new PatchInfo(config);
info.gen();
//build patch
PatchBuilder builder = new PatchBuilder(config);
builder.buildPatch();

ApkDecoder.patch(File oldFile, File newFile)函数中,会先对manifest文件进行检测,看其是否有更改,如果发现manifest的组件有新增,则抛出异常,因为目前Tinker暂不支持四大组件的新增。检测通过后解压apk文件,遍历新旧apk,交给ApkFilesVisitor进行处理。

//check manifest change first
manifestDecoder.patch(oldFile, newFile);
unzipApkFiles(oldFile, newFile);
Files.walkFileTree(mNewApkDir.toPath(), new ApkFilesVisitor(config, mNewApkDir.toPath(), mOldApkDir.toPath(), dexPatchDecoder, soPatchDecoder, resPatchDecoder));

ApkFilesVisitor的visitFile函数中,对于dex类型的文件,调用dexDecoder进行patch操作;对于so类型的文件,使用soDecoder进行patch操作;对于Res类型文件,使用resDecoder进行操作。

本文在下面主要是针对dexDecoder进行分析。

public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {

    Path relativePath = newApkPath.relativize(file);

    Path oldPath = oldApkPath.resolve(relativePath);

    File oldFile = null;
    //is a new file?!
    if (oldPath.toFile().exists()) {
        oldFile = oldPath.toFile();
    }
    String patternKey = relativePath.toString().replace("\\", "/");

    if (Utils.checkFileInPattern(config.mDexFilePattern, patternKey)) {
        //also treat duplicate file as unchanged
        if (Utils.checkFileInPattern(config.mResFilePattern, patternKey) && oldFile != null) {
            resDuplicateFiles.add(oldFile);
        }

        try {
            dexDecoder.patch(oldFile, file.toFile());
        } catch (Exception e) {
//                    e.printStackTrace();
            throw new RuntimeException(e);
        }
        return FileVisitResult.CONTINUE;
    }
    if (Utils.checkFileInPattern(config.mSoFilePattern, patternKey)) {
        //also treat duplicate file as unchanged
        if (Utils.checkFileInPattern(config.mResFilePattern, patternKey) && oldFile != null) {
            resDuplicateFiles.add(oldFile);
        }
        try {
            soDecoder.patch(oldFile, file.toFile());
        } catch (Exception e) {
//                    e.printStackTrace();
            throw new RuntimeException(e);
        }
        return FileVisitResult.CONTINUE;
    }
    if (Utils.checkFileInPattern(config.mResFilePattern, patternKey)) {
        try {
            resDecoder.patch(oldFile, file.toFile());
        } catch (Exception e) {
//                    e.printStackTrace();
            throw new RuntimeException(e);
        }
        return FileVisitResult.CONTINUE;
    }
    return FileVisitResult.CONTINUE;

DexDiffDecoder.patch(final File oldFile, final File newFile) 首先检测输入的dex文件中是否有不允许修改的类被修改了,如loader相关的类是不允许被修改的,这种情况下会抛出异常;如果dex是新增的,直接将该dex拷贝到结果文件;如果dex是修改的,收集增加和删除的class。

oldAndNewDexFilePairList将新旧dex对应关系保存起来,用于后面的分析。

excludedClassModifiedChecker.checkIfExcludedClassWasModifiedInNewDex(oldFile, newFile);
...
//new add file
if (oldFile == null || !oldFile.exists() || oldFile.length() == 0) {
    hasDexChanged = true;
    if (!config.mUsePreGeneratedPatchDex) {
        copyNewDexAndLogToDexMeta(newFile, newMd5, dexDiffOut);
        return true;
    }
}
...
// collect current old dex file and corresponding new dex file for further processing.
oldAndNewDexFilePairList.add(new AbstractMap.SimpleEntry<>(oldFile, newFile));

在后面UniqueDexDiffDecoder.patch中将新的dex文件加入到addedDexFiles。

public boolean patch(File oldFile, File newFile) throws IOException, TinkerPatchException {
    boolean added = super.patch(oldFile, newFile);
    if (added) {
        String name = newFile.getName();
        if (addedDexFiles.contains(name)) {
            throw new TinkerPatchException("illegal dex name, dex name should be unique, dex:" + name);
        } else {
            addedDexFiles.add(name);
        }
    }
    return added;
}

在patch完成后,会调用generatePatchInfoFile生成补丁文件。DexDiffDecoder.generatePatchInfoFile中首先遍历oldAndNewDexFilePairList,取出新旧文件对。判断新旧文件的MD5是否相等,不相等,说明有变化,会根据新旧文件创建DexPatchGenerator,DexPatchGenerator构造函数中包含了15个Dex区域的比较算法:

  • StringDataSectionDiffAlgorithm
  • TypeIdSectionDiffAlgorithm
  • ProtoIdSectionDiffAlgorithm
  • FieldIdSectionDiffAlgorithm
  • MethodIdSectionDiffAlgorithm
  • ClassDefSectionDiffAlgorithm
  • TypeListSectionDiffAlgorithm
  • AnnotationSetRefListSectionDiffAlgorithm
  • AnnotationSetSectionDiffAlgorithm
  • ClassDataSectionDiffAlgorithm
  • CodeSectionDiffAlgorithm
  • DebugInfoItemSectionDiffAlgorithm
  • AnnotationSectionDiffAlgorithm
  • StaticValueSectionDiffAlgorithm
  • AnnotationsDirectorySectionDiffAlgorithm

DexDiffDecoder.executeAndSaveTo(OutputStream out) 这个函数里面会根据上面的15个算法对dex的各个区域进行比较,最后生成dex文件的差异,这是整个dex diff算法的核心

以StringDataSectionDiffAlgorithm为例,算法流程如下:

获取oldDex中StringData区域的Item,并进行排序;
获取newDex中StringData区域的Item,并进行排序;
然后对ITEM依次比较
<0
 说明从老的dex中删除了该String,patchOperationList中添加Del操作;
\>0
 说明添加了该String,patchOperationList添加add操作;
=0
 说明都有该String, 记录oldIndexToNewIndexMap,oldOffsetToNewOffsetMap;
old item已到结尾
 剩下的item说明都是新增项,patchOperationList添加add操作;
new item已到结尾
 剩下的item说明都是删除项,patchOperationList添加del操作;
最后对对patchOperationList进行优化(
{OP_DEL idx} followed by {OP_ADD the_same_idx newItem} will be replaced by {OP_REPLACE idx newItem})

Dexdiff得到的最终生成产物就是针对原dex的一个操作序列。

接着对每个区域比较后会将比较的结果写入文件中,文件格式写在DexDataBuffe

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值