Tinker源碼分析(七):dex合成流程

本系列 Tinker 源碼解析基于 Tinker v1.9.12

前面講到了 Tinker 安裝補(bǔ)丁的流程,現(xiàn)在就詳細(xì)地來看下 dex 合成的代碼阀溶。代碼入口就在 DexDiffPatchInternal.tryRecoverDexFiles 中品追。

UpgradePatch

//we use destPatchFile instead of patchFile, because patchFile may be deleted during the patch process
if (!DexDiffPatchInternal.tryRecoverDexFiles(manager, signatureCheck, context, patchVersionDirectory, destPatchFile)) {
    TinkerLog.e(TAG, "UpgradePatch tryPatch:new patch recover, try patch dex failed");
    return false;
}

直接調(diào)用了 DexDiffPatchInternal.tryRecoverDexFiles 方法玄括。

tryRecoverDexFiles

protected static boolean tryRecoverDexFiles(Tinker manager, ShareSecurityCheck checker, Context context,
                                            String patchVersionDirectory, File patchFile) {
    // 檢查是否開啟支持dex補(bǔ)丁開關(guān)                                        
    if (!manager.isEnabledForDex()) {
        TinkerLog.w(TAG, "patch recover, dex is not enabled");
        return true;
    }
    // 檢查補(bǔ)丁包中的 dex_meta.txt 是否存在
    String dexMeta = checker.getMetaContentMap().get(DEX_META_FILE);

    if (dexMeta == null) {
        TinkerLog.w(TAG, "patch recover, dex is not contained");
        return true;
    }

    long begin = SystemClock.elapsedRealtime();
    // 到這個方法中執(zhí)行具體的操作
    boolean result = patchDexExtractViaDexDiff(context, patchVersionDirectory, dexMeta, patchFile);
    long cost = SystemClock.elapsedRealtime() - begin;
    TinkerLog.i(TAG, "recover dex result:%b, cost:%d", result, cost);
    return result;
}

tryRecoverDexFiles 方法開頭做了些校驗(yàn),最后又到 patchDexExtractViaDexDiff 中诵盼。

patchDexExtractViaDexDiff

private static boolean patchDexExtractViaDexDiff(Context context, String patchVersionDirectory, String meta, final File patchFile) {
    // dex補(bǔ)丁合成的路徑
    String dir = patchVersionDirectory + "/" + DEX_PATH + "/";
    // extractDexDiffInternals 這個方法是重點(diǎn);莶颉!风宁!
    if (!extractDexDiffInternals(context, dir, meta, patchFile, TYPE_DEX)) {
        TinkerLog.w(TAG, "patch recover, extractDiffInternals fail");
        return false;
    }

    // 把 tinker/patch-xxxxx/dex/ 下面的文件校驗(yàn)下,看看是否是合法的dex文件
    File dexFiles = new File(dir);
    File[] files = dexFiles.listFiles();
    List<File> legalFiles = new ArrayList<>();
    // may have directory in android o
    if (files != null) {
        for (File file : files) {
            final String fileName = file.getName();
            if (file.isFile()
                &&  (fileName.endsWith(ShareConstants.DEX_SUFFIX)
                  || fileName.endsWith(ShareConstants.JAR_SUFFIX)
                  || fileName.endsWith(ShareConstants.PATCH_SUFFIX))
            ) {
                legalFiles.add(file);
            }
        }
    }

    TinkerLog.i(TAG, "legal files to do dexopt: " + legalFiles);
    // 對 dex 做 opt 優(yōu)化
    final String optimizeDexDirectory = patchVersionDirectory + "/" + DEX_OPTIMIZE_PATH + "/";
    return dexOptimizeDexFiles(context, legalFiles, optimizeDexDirectory, patchFile);

}

在 patchDexExtractViaDexDiff 中可以看到蛹疯, dex 文件合成之后戒财,會對其做 opt 優(yōu)化。而合成的代碼就在 extractDexDiffInternals 里面捺弦。

extractDexDiffInternals 方法有點(diǎn)長饮寞。按照老規(guī)矩,我們分段來看列吼。

extractDexDiffInternals

private static boolean extractDexDiffInternals(Context context, String dir, String meta, File patchFile, int type) {
    
    // 讀取 dex_meta.txt 中的信息
    patchList.clear();
    ShareDexDiffPatchInfo.parseDexDiffPatchInfo(meta, patchList);

    if (patchList.isEmpty()) {
        TinkerLog.w(TAG, "extract patch list is empty! type:%s:", ShareTinkerInternals.getTypeString(type));
        return true;
    }
    

首先讀取 dex_meta.txt 中的信息幽崩,用“,”分割,保存到 patchList 中寞钥。

下面貼出一份 dex_meta.txt 的示例:

    classes.dex,,1a6e6d6a40eff95aa33ab06e07acd413,1a6e6d6a40eff95aa33ab06e07acd413,d865f383455abd6e3f70096109543644,2999635299,712828526,jar
    test.dex,,56900442eb5b7e1de45449d0685e6e00,56900442eb5b7e1de45449d0685e6e00,0,0,0,jar

dex_meta.txt 記錄著

  • name :補(bǔ)丁 dex 名字
  • path :補(bǔ)丁 dex 路徑
  • destMd5InDvm :合成新 dex 在 dvm 中的 md5 值
  • destMd5InArt :合成新 dex 在 art 中的 md5 值
  • dexDiffMd5 :補(bǔ)丁包 dex 文件的 md5 值
  • oldDexCrc :基準(zhǔn)包中對應(yīng) dex 的 crc 值
  • newDexCrc :合成新 dex 的 crc 值
  • dexMode :dex 類型慌申,為 jar 類型

接著往下看。

    File directory = new File(dir);
    if (!directory.exists()) {
       directory.mkdirs();
    }
    //I think it is better to extract the raw files from apk
    Tinker manager = Tinker.with(context);
    ZipFile apk = null;
    ZipFile patch = null;
    try {
       ApplicationInfo applicationInfo = context.getApplicationInfo();
       if (applicationInfo == null) {
           // Looks like running on a test Context, so just return without patching.
           TinkerLog.w(TAG, "applicationInfo == null!!!!");
           return false;
       }
       // 獲取到基準(zhǔn)包apk的路徑
       String apkPath = applicationInfo.sourceDir;
       // 基準(zhǔn)包文件
       apk = new ZipFile(apkPath);
       // 補(bǔ)丁包文件
       patch = new ZipFile(patchFile);
       if (checkClassNDexFiles(dir)) {
           TinkerLog.w(TAG, "class n dex file %s is already exist, and md5 match, just continue", ShareConstants.CLASS_N_APK_NAME);
           return true;
       }

然后獲取基本包和補(bǔ)丁包的路徑理郑,為下面合成做準(zhǔn)備蹄溉。

// 遍歷 ShareDexDiffPatchInfo
for (ShareDexDiffPatchInfo info : patchList) {
  long start = System.currentTimeMillis();

    // 補(bǔ)丁dex文件路徑
  final String infoPath = info.path;
  String patchRealPath;
  if (infoPath.equals("")) {
      patchRealPath = info.rawName;
  } else {
      patchRealPath = info.path + "/" + info.rawName;
  }

  String dexDiffMd5 = info.dexDiffMd5;
  String oldDexCrc = info.oldDexCrC;

    // 如果是 dvm 虛擬機(jī)環(huán)境,但是補(bǔ)丁dex是art環(huán)境的您炉,就跳過
  if (!isVmArt && info.destMd5InDvm.equals("0")) {
      TinkerLog.w(TAG, "patch dex %s is only for art, just continue", patchRealPath);
      continue;
  }
  String extractedFileMd5 = isVmArt ? info.destMd5InArt : info.destMd5InDvm;
  // 檢查 md5 值
  if (!SharePatchFileUtil.checkIfMd5Valid(extractedFileMd5)) {
      TinkerLog.w(TAG, "meta file md5 invalid, type:%s, name: %s, md5: %s", ShareTinkerInternals.getTypeString(type), info.rawName, extractedFileMd5);
      manager.getPatchReporter().onPatchPackageCheckFail(patchFile, BasePatchInternal.getMetaCorruptedCode(type));
      return false;
  }

  File extractedFile = new File(dir + info.realName);

  // 如果合成的dex文件已經(jīng)存在了
  if (extractedFile.exists()) {
      // 就校驗(yàn)合成的 dex 文件md5值柒爵,如果通過就跳過
      if (SharePatchFileUtil.verifyDexFileMd5(extractedFile, extractedFileMd5)) {
          //it is ok, just continue
          TinkerLog.w(TAG, "dex file %s is already exist, and md5 match, just continue", extractedFile.getPath());
          continue;
      } else {
          TinkerLog.w(TAG, "have a mismatch corrupted dex " + extractedFile.getPath());
          // 否則刪除文件
          extractedFile.delete();
      }
  } else {
      extractedFile.getParentFile().mkdirs();
  }

從這里開始,就是遍歷 patchList 中的記錄赚爵,進(jìn)行一個個 dex 文件合成了棉胀。一開頭會去校驗(yàn)合成的文件是否存在,存在的話就跳過冀膝,進(jìn)行下一個唁奢。

  ZipEntry patchFileEntry = patch.getEntry(patchRealPath);
  ZipEntry rawApkFileEntry = apk.getEntry(patchRealPath);

  if (oldDexCrc.equals("0")) {
      if (patchFileEntry == null) {
          TinkerLog.w(TAG, "patch entry is null. path:" + patchRealPath);
          manager.getPatchReporter().onPatchTypeExtractFail(patchFile, extractedFile, info.rawName, type);
          return false;
      }

      //it is a new file, but maybe we need to repack the dex file
      if (!extractDexFile(patch, patchFileEntry, extractedFile, info)) {
          TinkerLog.w(TAG, "Failed to extract raw patch file " + extractedFile.getPath());
          manager.getPatchReporter().onPatchTypeExtractFail(patchFile, extractedFile, info.rawName, type);
          return false;
      }
  } 

如果 oldDexCrc 為0,就說明基準(zhǔn)包中對應(yīng)的 oldDex 文件不存在畸写,直接按照 patch 信息重新打包 dex 即可驮瞧。

// 如果 dexDiffMd5 為 0, 就說明補(bǔ)丁包中沒有這個dex枯芬,但是基準(zhǔn)包中存在
  else if (dexDiffMd5.equals("0")) {
      // skip process old dex for real dalvik vm
      // 如果是 dvm 環(huán)境的無須做處理
      if (!isVmArt) {
          continue;
      }

      // 檢查基準(zhǔn)包中的 dex 是否為空
      if (rawApkFileEntry == null) {
          TinkerLog.w(TAG, "apk entry is null. path:" + patchRealPath);
          manager.getPatchReporter().onPatchTypeExtractFail(patchFile, extractedFile, info.rawName, type);
          return false;
      }

      //check source crc instead of md5 for faster
      // 檢查基準(zhǔn)包中的 dex 的 crc 值和 dex_meta.txt 中是否一致
      String rawEntryCrc = String.valueOf(rawApkFileEntry.getCrc());
      if (!rawEntryCrc.equals(oldDexCrc)) {
          TinkerLog.e(TAG, "apk entry %s crc is not equal, expect crc: %s, got crc: %s", patchRealPath, oldDexCrc, rawEntryCrc);
          manager.getPatchReporter().onPatchTypeExtractFail(patchFile, extractedFile, info.rawName, type);
          return false;
      }

      // Small patched dex generating strategy was disabled, we copy full original dex directly now.
      //patchDexFile(apk, patch, rawApkFileEntry, null, info, smallPatchInfoFile, extractedFile);
      // 直接復(fù)制 :copy full original dex directly now.
      extractDexFile(apk, rawApkFileEntry, extractedFile, info);

      // 復(fù)制完后校驗(yàn)一下md5值是否一致
      if (!SharePatchFileUtil.verifyDexFileMd5(extractedFile, extractedFileMd5)) {
          TinkerLog.w(TAG, "Failed to recover dex file when verify patched dex: " + extractedFile.getPath());
          manager.getPatchReporter().onPatchTypeExtractFail(patchFile, extractedFile, info.rawName, type);
          SharePatchFileUtil.safeDeleteFile(extractedFile);
          return false;
      }
  } 

上面這段代碼用來處理基準(zhǔn)包中有 oldDex 论笔,但是補(bǔ)丁包中沒有 dex 的情況采郎。

如果是 dvm 環(huán)境就跳過不處理即可,如果是 art 環(huán)境就把 oldDex 復(fù)制過去狂魔。

            else {
                // 檢查補(bǔ)丁包中 dex 是否存在
                if (patchFileEntry == null) {
                    TinkerLog.w(TAG, "patch entry is null. path:" + patchRealPath);
                    manager.getPatchReporter().onPatchTypeExtractFail(patchFile, extractedFile, info.rawName, type);
                    return false;
                }
                // 檢查補(bǔ)丁包中的 dex md5值是否合法
                if (!SharePatchFileUtil.checkIfMd5Valid(dexDiffMd5)) {
                    TinkerLog.w(TAG, "meta file md5 invalid, type:%s, name: %s, md5: %s", ShareTinkerInternals.getTypeString(type), info.rawName, dexDiffMd5);
                    manager.getPatchReporter().onPatchPackageCheckFail(patchFile, BasePatchInternal.getMetaCorruptedCode(type));
                    return false;
                }
                // 檢查基準(zhǔn)包中的 dex 是否存在
                if (rawApkFileEntry == null) {
                    TinkerLog.w(TAG, "apk entry is null. path:" + patchRealPath);
                    manager.getPatchReporter().onPatchTypeExtractFail(patchFile, extractedFile, info.rawName, type);
                    return false;
                }
                // 檢查基準(zhǔn)包中的 dex 的 crc 值是否一致
                String rawEntryCrc = String.valueOf(rawApkFileEntry.getCrc());
                if (!rawEntryCrc.equals(oldDexCrc)) {
                    TinkerLog.e(TAG, "apk entry %s crc is not equal, expect crc: %s, got crc: %s", patchRealPath, oldDexCrc, rawEntryCrc);
                    manager.getPatchReporter().onPatchTypeExtractFail(patchFile, extractedFile, info.rawName, type);
                    return false;
                }
                // 執(zhí)行合成操作
                patchDexFile(apk, patch, rawApkFileEntry, patchFileEntry, info, extractedFile);
                // 檢查合成出來的dex的 md5 值是否一致
                if (!SharePatchFileUtil.verifyDexFileMd5(extractedFile, extractedFileMd5)) {
                    TinkerLog.w(TAG, "Failed to recover dex file when verify patched dex: " + extractedFile.getPath());
                    manager.getPatchReporter().onPatchTypeExtractFail(patchFile, extractedFile, info.rawName, type);
                    SharePatchFileUtil.safeDeleteFile(extractedFile);
                    return false;
                }

                TinkerLog.w(TAG, "success recover dex file: %s, size: %d, use time: %d",
                    extractedFile.getPath(), extractedFile.length(), (System.currentTimeMillis() - start));
            }
        }
        if (!mergeClassNDexFiles(context, patchFile, dir)) {
            return false;
        }
    } catch (Throwable e) {
        throw new TinkerRuntimeException("patch " + ShareTinkerInternals.getTypeString(type) + " extract failed (" + e.getMessage() + ").", e);
    } finally {
        SharePatchFileUtil.closeZip(apk);
        SharePatchFileUtil.closeZip(patch);
    }
    return true;
}

最后蒜埋,就是基準(zhǔn)包和補(bǔ)丁包中都存在對應(yīng) dex 的情況了。

代碼一開始就是一堆的各種校驗(yàn)最楷,都通過后整份,調(diào)用 patchDexFile 執(zhí)行合成操作。合成完后再對合成的 dex 進(jìn)行md5校驗(yàn)籽孙。

patchDexFile

private static void patchDexFile(
    ZipFile baseApk, ZipFile patchPkg, ZipEntry oldDexEntry, ZipEntry patchFileEntry,
    ShareDexDiffPatchInfo patchInfo, File patchedDexFile) throws IOException {
    InputStream oldDexStream = null;
    InputStream patchFileStream = null;
    try {
        // 基準(zhǔn)包 dex 文件輸入流
        oldDexStream = new BufferedInputStream(baseApk.getInputStream(oldDexEntry));
        // 補(bǔ)丁包 dex 文件輸入流
        patchFileStream = (patchFileEntry != null ? new BufferedInputStream(patchPkg.getInputStream(patchFileEntry)) : null);

        final boolean isRawDexFile = SharePatchFileUtil.isRawDexFile(patchInfo.rawName);
        if (!isRawDexFile || patchInfo.isJarMode) {
            ZipOutputStream zos = null;
            try {
                // 合成 dex 文件的輸出流
                zos = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(patchedDexFile)));
                zos.putNextEntry(new ZipEntry(ShareConstants.DEX_IN_JAR));
                // Old dex is not a raw dex file.
                if (!isRawDexFile) {
                    ZipInputStream zis = null;
                    try {
                        zis = new ZipInputStream(oldDexStream);
                        ZipEntry entry;
                        while ((entry = zis.getNextEntry()) != null) {
                            if (ShareConstants.DEX_IN_JAR.equals(entry.getName())) break;
                        }
                        if (entry == null) {
                            throw new TinkerRuntimeException("can't recognize zip dex format file:" + patchedDexFile.getAbsolutePath());
                        }
                        new DexPatchApplier(zis, patchFileStream).executeAndSaveTo(zos);
                    } finally {
                        StreamUtil.closeQuietly(zis);
                    }
                } else {
                    new DexPatchApplier(oldDexStream, patchFileStream).executeAndSaveTo(zos);
                }
                zos.closeEntry();
            } finally {
                StreamUtil.closeQuietly(zos);
            }
        } else {
            new DexPatchApplier(oldDexStream, patchFileStream).executeAndSaveTo(patchedDexFile);
        }
    } finally {
        StreamUtil.closeQuietly(oldDexStream);
        StreamUtil.closeQuietly(patchFileStream);
    }
}

在 patchDexFile 中烈评,拿到基準(zhǔn)包 dex 文件的 InputStream 和補(bǔ)丁包 dex 文件的 InputStream ,然后利用 DexPatchApplier 把這兩個流合成一個 dex 文件犯建。

public void executeAndSaveTo(OutputStream out) throws IOException {
    // Before executing, we should check if this patch can be applied to
    // old dex we passed in.
    byte[] oldDexSign = this.oldDex.computeSignature(false);
    if (oldDexSign == null) {
        throw new IOException("failed to compute old dex's signature.");
    }
    if (this.patchFile == null) {
        throw new IllegalArgumentException("patch file is null.");
    }
    byte[] oldDexSignInPatchFile = this.patchFile.getOldDexSignature();
    if (CompareUtils.uArrCompare(oldDexSign, oldDexSignInPatchFile) != 0) {
        throw new IOException(
                String.format(
                        "old dex signature mismatch! expected: %s, actual: %s",
                        Arrays.toString(oldDexSign),
                        Arrays.toString(oldDexSignInPatchFile)
                )
        );
    }

    // Firstly, set sections' offset after patched, sort according to their offset so that
    // the dex lib of aosp can calculate section size.
    TableOfContents patchedToc = this.patchedDex.getTableOfContents();

    patchedToc.header.off = 0;
    patchedToc.header.size = 1;
    patchedToc.mapList.size = 1;

    patchedToc.stringIds.off
            = this.patchFile.getPatchedStringIdSectionOffset();
    patchedToc.typeIds.off
            = this.patchFile.getPatchedTypeIdSectionOffset();
    patchedToc.typeLists.off
            = this.patchFile.getPatchedTypeListSectionOffset();
    patchedToc.protoIds.off
            = this.patchFile.getPatchedProtoIdSectionOffset();
    patchedToc.fieldIds.off
            = this.patchFile.getPatchedFieldIdSectionOffset();
    patchedToc.methodIds.off
            = this.patchFile.getPatchedMethodIdSectionOffset();
    patchedToc.classDefs.off
            = this.patchFile.getPatchedClassDefSectionOffset();
    patchedToc.mapList.off
            = this.patchFile.getPatchedMapListSectionOffset();
    patchedToc.stringDatas.off
            = this.patchFile.getPatchedStringDataSectionOffset();
    patchedToc.annotations.off
            = this.patchFile.getPatchedAnnotationSectionOffset();
    patchedToc.annotationSets.off
            = this.patchFile.getPatchedAnnotationSetSectionOffset();
    patchedToc.annotationSetRefLists.off
            = this.patchFile.getPatchedAnnotationSetRefListSectionOffset();
    patchedToc.annotationsDirectories.off
            = this.patchFile.getPatchedAnnotationsDirectorySectionOffset();
    patchedToc.encodedArrays.off
            = this.patchFile.getPatchedEncodedArraySectionOffset();
    patchedToc.debugInfos.off
            = this.patchFile.getPatchedDebugInfoSectionOffset();
    patchedToc.codes.off
            = this.patchFile.getPatchedCodeSectionOffset();
    patchedToc.classDatas.off
            = this.patchFile.getPatchedClassDataSectionOffset();
    patchedToc.fileSize
            = this.patchFile.getPatchedDexSize();

    Arrays.sort(patchedToc.sections);

    patchedToc.computeSizesFromOffsets();

    // Secondly, run patch algorithms according to sections' dependencies.
    this.stringDataSectionPatchAlg = new StringDataSectionPatchAlgorithm(
            patchFile, oldDex, patchedDex, oldToPatchedIndexMap
    );
    this.typeIdSectionPatchAlg = new TypeIdSectionPatchAlgorithm(
            patchFile, oldDex, patchedDex, oldToPatchedIndexMap
    );
    this.protoIdSectionPatchAlg = new ProtoIdSectionPatchAlgorithm(
            patchFile, oldDex, patchedDex, oldToPatchedIndexMap
    );
    this.fieldIdSectionPatchAlg = new FieldIdSectionPatchAlgorithm(
            patchFile, oldDex, patchedDex, oldToPatchedIndexMap
    );
    this.methodIdSectionPatchAlg = new MethodIdSectionPatchAlgorithm(
            patchFile, oldDex, patchedDex, oldToPatchedIndexMap
    );
    this.classDefSectionPatchAlg = new ClassDefSectionPatchAlgorithm(
            patchFile, oldDex, patchedDex, oldToPatchedIndexMap
    );
    this.typeListSectionPatchAlg = new TypeListSectionPatchAlgorithm(
            patchFile, oldDex, patchedDex, oldToPatchedIndexMap
    );
    this.annotationSetRefListSectionPatchAlg = new AnnotationSetRefListSectionPatchAlgorithm(
            patchFile, oldDex, patchedDex, oldToPatchedIndexMap
    );
    this.annotationSetSectionPatchAlg = new AnnotationSetSectionPatchAlgorithm(
            patchFile, oldDex, patchedDex, oldToPatchedIndexMap
    );
    this.classDataSectionPatchAlg = new ClassDataSectionPatchAlgorithm(
            patchFile, oldDex, patchedDex, oldToPatchedIndexMap
    );
    this.codeSectionPatchAlg = new CodeSectionPatchAlgorithm(
            patchFile, oldDex, patchedDex, oldToPatchedIndexMap
    );
    this.debugInfoSectionPatchAlg = new DebugInfoItemSectionPatchAlgorithm(
            patchFile, oldDex, patchedDex, oldToPatchedIndexMap
    );
    this.annotationSectionPatchAlg = new AnnotationSectionPatchAlgorithm(
            patchFile, oldDex, patchedDex, oldToPatchedIndexMap
    );
    this.encodedArraySectionPatchAlg = new StaticValueSectionPatchAlgorithm(
            patchFile, oldDex, patchedDex, oldToPatchedIndexMap
    );
    this.annotationsDirectorySectionPatchAlg = new AnnotationsDirectorySectionPatchAlgorithm(
            patchFile, oldDex, patchedDex, oldToPatchedIndexMap
    );

    this.stringDataSectionPatchAlg.execute();
    this.typeIdSectionPatchAlg.execute();
    this.typeListSectionPatchAlg.execute();
    this.protoIdSectionPatchAlg.execute();
    this.fieldIdSectionPatchAlg.execute();
    this.methodIdSectionPatchAlg.execute();
    this.annotationSectionPatchAlg.execute();
    this.annotationSetSectionPatchAlg.execute();
    this.annotationSetRefListSectionPatchAlg.execute();
    this.annotationsDirectorySectionPatchAlg.execute();
    this.debugInfoSectionPatchAlg.execute();
    this.codeSectionPatchAlg.execute();
    this.classDataSectionPatchAlg.execute();
    this.encodedArraySectionPatchAlg.execute();
    this.classDefSectionPatchAlg.execute();

    // Thirdly, write header, mapList. Calculate and write patched dex's sign and checksum.
    Dex.Section headerOut = this.patchedDex.openSection(patchedToc.header.off);
    patchedToc.writeHeader(headerOut);

    Dex.Section mapListOut = this.patchedDex.openSection(patchedToc.mapList.off);
    patchedToc.writeMap(mapListOut);

    this.patchedDex.writeHashes();

    // Finally, write patched dex to file.
    this.patchedDex.writeTo(out);
}

而 DexPatchApplier 里面合流操作的代碼是需要根據(jù) Tinker 的 DexDiff 算法來的讲冠。大致就是把兩個 Dex 文件的每個分區(qū)做 merge 操作。

這里先留一個坑适瓦。等以后把 DexDiff 算法看明白了再補(bǔ)上竿开。

另外,dodola 寫了一篇 Tinker Dexdiff算法解析玻熙,有需要的同學(xué)可以看下否彩。

那么 dex 合成的流程就到這吧。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末嗦随,一起剝皮案震驚了整個濱河市列荔,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌称杨,老刑警劉巖肌毅,帶你破解...
    沈念sama閱讀 221,695評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異姑原,居然都是意外死亡悬而,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,569評論 3 399
  • 文/潘曉璐 我一進(jìn)店門锭汛,熙熙樓的掌柜王于貴愁眉苦臉地迎上來笨奠,“玉大人,你說我怎么就攤上這事唤殴“闫牛” “怎么了?”我有些...
    開封第一講書人閱讀 168,130評論 0 360
  • 文/不壞的土叔 我叫張陵朵逝,是天一觀的道長蔚袍。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么啤咽? 我笑而不...
    開封第一講書人閱讀 59,648評論 1 297
  • 正文 為了忘掉前任晋辆,我火速辦了婚禮,結(jié)果婚禮上宇整,老公的妹妹穿的比我還像新娘瓶佳。我一直安慰自己,他們只是感情好鳞青,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,655評論 6 397
  • 文/花漫 我一把揭開白布霸饲。 她就那樣靜靜地躺著,像睡著了一般臂拓。 火紅的嫁衣襯著肌膚如雪厚脉。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,268評論 1 309
  • 那天胶惰,我揣著相機(jī)與錄音器仗,去河邊找鬼。 笑死童番,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的威鹿。 我是一名探鬼主播剃斧,決...
    沈念sama閱讀 40,835評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼忽你!你這毒婦竟也來了幼东?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,740評論 0 276
  • 序言:老撾萬榮一對情侶失蹤科雳,失蹤者是張志新(化名)和其女友劉穎根蟹,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體糟秘,經(jīng)...
    沈念sama閱讀 46,286評論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡简逮,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,375評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了尿赚。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片散庶。...
    茶點(diǎn)故事閱讀 40,505評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖凌净,靈堂內(nèi)的尸體忽然破棺而出悲龟,到底是詐尸還是另有隱情,我是刑警寧澤冰寻,帶...
    沈念sama閱讀 36,185評論 5 350
  • 正文 年R本政府宣布须教,位于F島的核電站,受9級特大地震影響斩芭,放射性物質(zhì)發(fā)生泄漏轻腺。R本人自食惡果不足惜乐疆,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,873評論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望约计。 院中可真熱鬧诀拭,春花似錦、人聲如沸煤蚌。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,357評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽尉桩。三九已至筒占,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間蜘犁,已是汗流浹背翰苫。 一陣腳步聲響...
    開封第一講書人閱讀 33,466評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留这橙,地道東北人奏窑。 一個月前我還...
    沈念sama閱讀 48,921評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像屈扎,于是被迫代替她去往敵國和親埃唯。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,515評論 2 359

推薦閱讀更多精彩內(nèi)容

  • 先來個小花壓壓驚!后面重戲登場 不知大家知不知道“Rainbow清潔系統(tǒng)”這樣一個黑黑的模蜡,大大的漠趁,貌不驚人的家伙,...
    靜子靜悄悄閱讀 235評論 0 2
  • 這個時間是吃飯的忍疾,以前闯传。看到陌生的臉膝昆,一重又一重的山丸边。 買了幾件衣服而已,默然于人世間荚孵。 ...
    Lan_9e0f閱讀 218評論 0 2
  • 近些日子自覺心浮氣躁妹窖,若是在瀏覽社交軟件時看到什么令人不悅的消息,更是一石激起千層浪收叶,以致被影響的思緒久久不能平復(fù)...
    嘉月清客閱讀 362評論 0 4
  • 南朝吳均自富陽至桐廬漂流骄呼,見“奇山異水,天下獨(dú)絕”,心下歡喜蜓萄,一定要將這一切說與好友朱元思聽隅茎,說它能夠使“鳶飛戾天...
    莫非仙閱讀 709評論 0 11