【原】Android熱更新開源項目Tinker源碼解析系列之一:Dex熱更新 Tinker是微信的第一個開源項目,主要用於安卓應用bug的熱修複和功能的迭代。 Tinker github地址:https://github.com/Tencent/tinker 首先向微信致敬,感謝毫無保留的開源出了這 ...
【原】Android熱更新開源項目Tinker源碼解析系列之一:Dex熱更新
Tinker是微信的第一個開源項目,主要用於安卓應用bug的熱修複和功能的迭代。
Tinker github地址:https://github.com/Tencent/tinker
首先向微信致敬,感謝毫無保留的開源出了這麼一款優秀的熱更新項目。
因Tinker支持Dex,資源文件及so文件的熱更新,本系列將從以下三個方面對Tinker進行源碼解析:
- Android熱更新開源項目Tinker源碼解析系列之一:Dex熱更新
- Android熱更新開源項目Tinker源碼解析系列之二:資源熱更新
- Android熱更新開源項目Tinker源碼解析系類之三:so熱更新
Tinker中Dex的熱更新也主要分為三個部分,本文也將從這三個方面進行分析:
- 生成補丁流程
- 補丁包下發成功後合成全量Dex流程
- 生成全量Dex後的載入流程
轉載請標明本文來源:http://www.cnblogs.com/yyangblog/p/6249715.html
更多內容歡迎star作者的github:https://github.com/LaurenceYang/article
如果發現本文有什麼問題和任何建議,也隨時歡迎交流~
一、生成補丁流程
當在命令行裡面調用tinkerPatchRelease任務時會調用com.tencent.tinker.build.patch.Runner.tinkerPatch()進行生成補丁生成過程。
1 //gen patch 2 ApkDecoder decoder = new ApkDecoder(config); 3 decoder.onAllPatchesStart(); 4 decoder.patch(config.mOldApkFile, config.mNewApkFile); 5 decoder.onAllPatchesEnd(); 6 7 //gen meta file and version file 8 PatchInfo info = new PatchInfo(config); 9 info.gen(); 10 11 //build patch 12 PatchBuilder builder = new PatchBuilder(config); 13 builder.buildPatch();
ApkDecoder.patch(File oldFile, File newFile)函數中,
會先對manifest文件進行檢測,看其是否有更改,如果發現manifest的組件有新增,則拋出異常,因為目前Tinker暫不支持四大組件的新增。
檢測通過後解壓apk文件,遍歷新舊apk,交給ApkFilesVisitor進行處理。
1 //check manifest change first 2 manifestDecoder.patch(oldFile, newFile); 3 4 unzipApkFiles(oldFile, newFile); 5 6 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進行分析。
1 public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { 2 3 Path relativePath = newApkPath.relativize(file); 4 5 Path oldPath = oldApkPath.resolve(relativePath); 6 7 File oldFile = null; 8 //is a new file?! 9 if (oldPath.toFile().exists()) { 10 oldFile = oldPath.toFile(); 11 } 12 String patternKey = relativePath.toString().replace("\\", "/"); 13 14 if (Utils.checkFileInPattern(config.mDexFilePattern, patternKey)) { 15 //also treat duplicate file as unchanged 16 if (Utils.checkFileInPattern(config.mResFilePattern, patternKey) && oldFile != null) { 17 resDuplicateFiles.add(oldFile); 18 } 19 20 try { 21 dexDecoder.patch(oldFile, file.toFile()); 22 } catch (Exception e) { 23 // e.printStackTrace(); 24 throw new RuntimeException(e); 25 } 26 return FileVisitResult.CONTINUE; 27 } 28 if (Utils.checkFileInPattern(config.mSoFilePattern, patternKey)) { 29 //also treat duplicate file as unchanged 30 if (Utils.checkFileInPattern(config.mResFilePattern, patternKey) && oldFile != null) { 31 resDuplicateFiles.add(oldFile); 32 } 33 try { 34 soDecoder.patch(oldFile, file.toFile()); 35 } catch (Exception e) { 36 // e.printStackTrace(); 37 throw new RuntimeException(e); 38 } 39 return FileVisitResult.CONTINUE; 40 } 41 if (Utils.checkFileInPattern(config.mResFilePattern, patternKey)) { 42 try { 43 resDecoder.patch(oldFile, file.toFile()); 44 } catch (Exception e) { 45 // e.printStackTrace(); 46 throw new RuntimeException(e); 47 } 48 return FileVisitResult.CONTINUE; 49 } 50 return FileVisitResult.CONTINUE;
DexDiffDecoder.patch(final File oldFile, final File newFile)
首先檢測輸入的dex文件中是否有不允許修改的類被修改了,如loader相關的類是不允許被修改的,這種情況下會拋出異常;
如果dex是新增的,直接將該dex拷貝到結果文件;
如果dex是修改的,收集增加和刪除的class。oldAndNewDexFilePairList將新舊dex對應關係保存起來,用於後面的分析。
1 excludedClassModifiedChecker.checkIfExcludedClassWasModifiedInNewDex(oldFile, newFile); 2 ... 3 //new add file 4 if (oldFile == null || !oldFile.exists() || oldFile.length() == 0) { 5 hasDexChanged = true; 6 if (!config.mUsePreGeneratedPatchDex) { 7 copyNewDexAndLogToDexMeta(newFile, newMd5, dexDiffOut); 8 return true; 9 } 10 } 11 ... 12 // collect current old dex file and corresponding new dex file for further processing. 13 oldAndNewDexFilePairList.add(new AbstractMap.SimpleEntry<>(oldFile, newFile));
UniqueDexDiffDecoder.patch中將新的dex文件加入到addedDexFiles。
1 public boolean patch(File oldFile, File newFile) throws IOException, TinkerPatchException { 2 boolean added = super.patch(oldFile, newFile); 3 if (added) { 4 String name = newFile.getName(); 5 if (addedDexFiles.contains(name)) { 6 throw new TinkerPatchException("illegal dex name, dex name should be unique, dex:" + name); 7 } else { 8 addedDexFiles.add(name); 9 } 10 } 11 return added; 12 }
在patch完成後,會調用generatePatchInfoFile生成補丁文件。
DexFiffDecoder.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的一個操作序列。
關於DexDiff演算法,更加詳細的介紹可以參考https://www.zybuluo.com/dodola/note/554061,演算法名曰二路歸併。
對每個區域比較後會將比較的結果寫入文件中,文件格式寫在DexDataBuffer中
1 private void writeResultToStream(OutputStream os) throws IOException { 2 DexDataBuffer buffer = new DexDataBuffer(); 3 buffer.write(DexPatchFile.MAGIC); 4 buffer.writeShort(DexPatchFile.CURRENT_VERSION); 5 buffer.writeInt(this.patchedDexSize); 6 // we will return here to write firstChunkOffset later. 7 int posOfFirstChunkOffsetField = buffer.position(); 8 buffer.writeInt(0); 9 buffer.writeInt(this.patchedStringIdsOffset); 10 buffer.writeInt(this.patchedTypeIdsOffset); 11 buffer.writeInt(this.patchedProtoIdsOffset); 12 buffer.writeInt(this.patchedFieldIdsOffset); 13 buffer.writeInt(this.patchedMethodIdsOffset); 14 buffer.writeInt(this.patchedClassDefsOffset); 15 buffer.writeInt(this.patchedMapListOffset); 16 buffer.writeInt(this.patchedTypeListsOffset); 17 buffer.writeInt(this.patchedAnnotationSetRefListItemsOffset); 18 buffer.writeInt(this.patchedAnnotationSetItemsOffset); 19 buffer.writeInt(this.patchedClassDataItemsOffset); 20 buffer.writeInt(this.patchedCodeItemsOffset); 21 buffer.writeInt(this.patchedStringDataItemsOffset); 22 buffer.writeInt(this.patchedDebugInfoItemsOffset); 23 buffer.writeInt(this.patchedAnnotationItemsOffset); 24 buffer.writeInt(this.patchedEncodedArrayItemsOffset); 25 buffer.writeInt(this.patchedAnnotationsDirectoryItemsOffset); 26 buffer.write(this.oldDex.computeSignature(false)); 27 int firstChunkOffset = buffer.position(); 28 buffer.position(posOfFirstChunkOffsetField); 29 buffer.writeInt(firstChunkOffset); 30 buffer.position(firstChunkOffset); 31 32 writePatchOperations(buffer, this.stringDataSectionDiffAlg.getPatchOperationList()); 33 writePatchOperations(buffer, this.typeIdSectionDiffAlg.getPatchOperationList()); 34 writePatchOperations(buffer, this.typeListSectionDiffAlg.getPatchOperationList()); 35 writePatchOperations(buffer, this.protoIdSectionDiffAlg.getPatchOperationList()); 36 writePatchOperations(buffer, this.fieldIdSectionDiffAlg.getPatchOperationList()); 37 writePatchOperations(buffer, this.methodIdSectionDiffAlg.getPatchOperationList()); 38 writePatchOperations(buffer, this.annotationSectionDiffAlg.getPatchOperationList()); 39 writePatchOperations(buffer, this.annotationSetSectionDiffAlg.getPatchOperationList()); 40 writePatchOperations(buffer, this.annotationSetRefListSectionDiffAlg.getPatchOperationList()); 41 writePatchOperations(buffer, this.annotationsDirectorySectionDiffAlg.getPatchOperationList()); 42 writePatchOperations(buffer, this.debugInfoSectionDiffAlg.getPatchOperationList()); 43 writePatchOperations(buffer, this.codeSectionDiffAlg.getPatchOperationList()); 44 writePatchOperations(buffer, this.classDataSectionDiffAlg.getPatchOperationList()); 45 writePatchOperations(buffer, this.encodedArraySectionDiffAlg.getPatchOperationList()); 46 writePatchOperations(buffer, this.classDefSectionDiffAlg.getPatchOperationList()); 47 48 byte[] bufferData = buffer.array(); 49 os.write(bufferData); 50 os.flush(); 51 }
生成的文件以dex結尾,但需要註意的是,它不是真正的dex文件,其格式可參考DexDataBuffer類。
二、補丁包下發成功後合成全量Dex流程
當app收到伺服器下發的補丁後,會觸發DefaultPatchListener.onPatchReceived事件,
調用TinkerPatchService.runPatchService啟動patch進程進行補丁patch工作。
UpgradePatch.tryPatch()中會首先檢查補丁的合法性,簽名,以及是否安裝過補丁,檢查通過後會嘗試dex,so以及res文件的patch。
本文中主要分析DexDiffPatchInternal.tryRecoverDexFiles,討論dex的patch過程。
1 DexDiffPatchInternal.tryRecoverDexFiles 2 BsDiffPatchInternal.tryRecoverLibraryFiles 3 ResDiffPatchInternal.tryRecoverResourceFiles 4 rewritePatchInfoFileWithLock
tryRecoverDexFiles調用DexDiffPatchInternal.patchDexFile,
最終通過DexPatchApplier.executeAndSaveTo進行執行及生產全量dex。
1 private static void patchDexFile( 2 ZipFile baseApk, ZipFile patchPkg, ZipEntry oldDexEntry, ZipEntry patchFileEntry, 3 ShareDexDiffPatchInfo patchInfo, File patchedDexFile) throws IOException { 4 InputStream oldDexStream = null; 5 InputStream patchFileStream = null; 6 try { 7 oldDexStream = baseApk.getInputStream(oldDexEntry); 8 patchFileStream = (patchFileEntry != null ? patchPkg.getInputStream(patchFileEntry) : null); 9 10 final boolean isRawDexFile = SharePatchFileUtil.isRawDexFile(patchInfo.rawName); 11 if (!isRawDexFile || patchInfo.isJarMode) { 12 ZipOutputStream zos = null; 13 try { 14 zos = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(patchedDexFile))); 15 zos.putNextEntry(new ZipEntry(ShareConstants.DEX_IN_JAR)); 16 // Old dex is not a raw dex file. 17 if (!isRawDexFile) { 18 ZipInputStream zis = null; 19 try { 20 zis = new ZipInputStream(oldDexStream); 21 ZipEntry entry; 22 while ((entry = zis.getNextEntry()) != null) { 23 if (ShareConstants.DEX_IN_JAR.equals(entry.getName())) break; 24 } 25 if (entry == null) { 26 throw new TinkerRuntimeException("can't recognize zip dex format file:" + patchedDexFile.getAbsolutePath()); 27 } 28 new DexPatchApplier(zis, (int) entry.getSize(), patchFileStream).executeAndSaveTo(zos); 29 } finally { 30 SharePatchFileUtil.closeQuietly(zis); 31 } 32 } else { 33 new DexPatchApplier(oldDexStream, (int) oldDexEntry.getSize(), patchFileStream).executeAndSaveTo(zos); 34 } 35 zos.closeEntry(); 36 } finally { 37 SharePatchFileUtil.closeQuietly(zos); 38 } 39 } else { 40 new DexPatchApplier(oldDexStream, (int) oldDexEntry.getSize(), patchFileStream).executeAndSaveTo(patchedDexFile); 41 } 42 } finally { 43 SharePatchFileUtil.closeQuietly(oldDexStream); 44 SharePatchFileUtil.closeQuietly(patchFileStream); 45 } 46 }
DexPatchApplier.executeAndSaveTo(OutputStream out)中會對15個dex區域進行patch操作,
針對old dex和patch dex進行合併,生成全量dex文件。
1 public void executeAndSaveTo(OutputStream out) throws IOException { 2 // Before executing, we should check if this patch can be applied to 3 // old dex we passed in. 4 // 首先old apk的簽名和patchfile所攜帶的old apk簽名是否一致,不一致則拋出異常 5 byte[] oldDexSign = this.oldDex.computeSignature(false); 6 if (oldDexSign == null) { 7 throw new IOException("failed to compute old dex's signature."); 8 } 9 10 if (this.patchFile != null) { 11 byte[] oldDexSignInPatchFile = this.patchFile.getOldDexSignature(); 12 if (CompareUtils.uArrCompare(oldDexSign, oldDexSignInPatchFile) != 0) { 13 throw new IOException( 14 String.format( 15 "old dex signature mismatch! expected: %s, actual: %s", 16 Arrays.toString(oldDexSign), 17 Arrays.toString(oldDexSignInPatchFile) 18 ) 19 ); 20 } 21 } 22 23 String oldDexSignStr = Hex.toHexString(oldDexSign); 24 25 // Firstly, set sections' offset after patched, sort according to their offset so that 26 // the dex lib of aosp can calculate section size. 27 // patchedDex是最終合成的dex,首先設定各個區域的偏移量 28 TableOfContents patchedToc = this.patchedDex.getTableOfContents(); 29 30 patchedToc.header.off = 0; 31 patchedToc.header.size = 1; 32 patchedToc.mapList.size = 1; 33 34 if (extraInfoFile == null || !extraInfoFile.isAffectedOldDex(this.oldDexSignStr)) { 35 patchedToc.stringIds.off 36 = this.patchFile.getPatchedStringIdSectionOffset(); 37 patchedToc.typeIds.off 38 = this.patchFile.getPatchedTypeIdSectionOffset(); 39 patchedToc.typeLists.off 40 = this.patchFile.getPatchedTypeListSectionOffset(); 41 patchedToc.protoIds.off 42 = this.patchFile.getPatchedProtoIdSectionOffset(); 43 patchedToc.fieldIds.off 44 = this.patchFile.getPatchedFieldIdSectionOffset(); 45 patchedToc.methodIds.off 46 = this.patchFile.getPatchedMethodIdSectionOffset(); 47 patchedToc.classDefs.off 48 = this.patchFile.getPatchedClassDefSectionOffset(); 49 patchedToc.mapList.off 50 = this.patchFile.getPatchedMapListSectionOffset(); 51 patchedToc.stringDatas.off 52 = this.patchFile.getPatchedStringDataSectionOffset(); 53 patchedToc.annotations.off 54 = this.patchFile.getPatchedAnnotationSectionOffset(); 55 patchedToc.annotationSets.off 56 = this.patchFile.getPatchedAnnotationSetSectionOffset(); 57 patchedToc.annotationSetRefLists.off 58 = this.patchFile.getPatchedAnnotationSetRefListSectionOffset(); 59 patchedToc.annotationsDirectories.off 60 = this.patchFile.getPatchedAnnotationsDirectorySectionOffset(); 61 patchedToc.encodedArrays.off 62 = this.patchFile.getPatchedEncodedArraySectionOffset(); 63 patchedToc.debugInfos.off 64 = this.patchFile.getPatchedDebugInfoSectionOffset(); 65 patchedToc.codes.off 66 = this.patchFile.getPatchedCodeSectionOffset(); 67 patchedToc.classDatas.off 68 = this.patchFile.getPatchedClassDataSectionOffset(); 69 patchedToc.fileSize 70 = this.patchFile.getPatchedDexSize(); 71 } else { 72 ... 73 } 74 75 Arrays.sort(patchedToc.sections); 76 77 patchedToc.computeSizesFromOffsets(); 78 79 // Secondly, run patch algorithms according to sections' dependencies. 80 // 對每個區域進行patch操作 81 this.stringDataSectionPatchAlg = new StringDataSectionPatchAlgorithm( 82 patchFile, oldDex, patchedDex, oldToFullPatchedIndexMap, 83 patchedToSmallPatchedIndexMap, extraInfoFile 84 ); 85 this.typeIdSectionPatchAlg = new TypeIdSectionPatchAlgorithm( 86 patchFile, oldDex, patchedDex, oldToFullPatchedIndexMap, 87 patchedToSmallPatchedIndexMap, extraInfoFile 88 ); 89 this.protoIdSectionPatchAlg = new ProtoIdSectionPatchAlgorithm( 90 patchFile, oldDex, patchedDex, oldToFullPatchedIndexMap, 91 patchedToSmallPatchedIndexMap, extraInfoFile 92 ); 93 this.fieldIdSectionPatchAlg = new FieldIdSectionPatchAlgorithm( 94 patchFile, oldDex, patchedDex, oldToFullPatchedIndexMap, 95 patchedToSmallPatchedIndexMap, extraInfoFile 96 ); 97 this.methodIdSectionPatchAlg = new MethodIdSectionPatchAlgorithm( 98 patchFile, oldDex, patchedDex, oldToFullPatchedIndexMap, 99 patchedToSmallPatchedIndexMap, extraInfoFile 100 ); 101 this.classDefSectionPatchAlg = new ClassDefSectionPatchAlgorithm( 102 patchFile, oldDex, patchedDex, oldToFullPatchedIndexMap, 103 patchedToSmallPatchedIndexMap, extraInfoFile 104 ); 105 this.typeListSectionPatchAlg = new TypeListSectionPatchAlgorithm( 106 patchFile, oldDex, patchedDex, oldToFullPatchedIndexMap, 107 patchedToSmallPatchedIndexMap, extraInfoFile 108 ); 109 this.annotationSetRefListSectionPatchAlg = new AnnotationSetRefListSectionPatchAlgorithm( 110 patchFile, oldDex, patchedDex, oldToFullPatchedIndexMap, 111 patchedToSmallPatchedIndexMap, extraInfoFile 112 ); 113 this.annotationSetSectionPatchAlg = new AnnotationSetSectionPatchAlgorithm( 114 patchFile, oldDex, patchedDex, oldToFullPatchedIndexMap, 115 patchedToSmallPatchedIndexMap, extraInfoFile 116 ); 117 this.classDataSectionPatchAlg = new ClassDataSectionPatchAlgorithm( 118 patchFile, oldDex, patchedDex, oldToFullPatchedIndexMap, 119 patchedToSmallPatchedIndexMap, extraInfoFile 120 ); 121 this.codeSectionPatchAlg = new CodeSectionPatchAlgorithm( 122 patchFile, oldDex, patchedDex, oldToFullPatchedIndexMap, 123 patchedToSmallPatchedIndexMap, extraInfoFile 124 ); 125 this.debugInfoSectionPatchAlg = new DebugInfoItemSectionPatchAlgorithm( 126 patchFile, oldDex, patchedDex, oldToFullPatchedIndexMap, 127 patchedToSmallPatchedIndexMap, extraInfoFile 128 ); 129 this.annotationSectionPatchAlg = new AnnotationSectionPatchAlgorithm( 130 patchFile, oldDex, patchedDex, oldToFullPatchedIndexMap, 131 patchedToSmallPatchedIndexMap, extraInfoFile 132 ); 133 this.encodedArraySectionPatchAlg = new StaticValueSectionPatchAlgorithm( 134 patchFile, oldDex, patchedDex, oldToFullPatchedIndexMap, 135 patchedToSmallPatchedIndexMap, extraInfoFile 136 ); 137 this.annotationsDirectorySectionPatchAlg = new AnnotationsDirectorySectionPatchAlgorithm( 138 patchFile, oldDex, patchedDex, oldToFullPatchedIndexMap, 139 patchedToSmallPatchedIndexMap, extraInfoFile 140 ); 141 142 this.stringDataSectionPatchAlg.execute(); 143 this.typeIdSectionPatchAlg.execute(); 144 this.typeListSectionPatchAlg.execute(); 145 this.protoIdSectionPatchAlg.execute(); 146 this.fieldIdSectionPatchAlg.execute(); 147 this.methodIdSectionPatchAlg.execute(); 148 Runtime.getRuntime().gc(); 149 this.annotationSectionPatchAlg.execute(); 150 this.annotationSetSectionPatchAlg.execute(); 151 this.annotationSetRefListSectionPatchAlg.execute(); 152 this.annotationsDirectorySectionPatchAlg.execute(); 153 Runtime.getRuntime().gc(); 154 this.debugInfoSectionPatchAlg.execute(); 155 this.codeSectionPatchAlg.execute(); 156 Runtime.getRuntime().gc(); 157 this.classDataSectionPatchAlg.execute(); 158 this.encodedArraySectionPatchAlg.execute(); 159 this.classDefSectionPatchAlg.execute(); 160 Runtime.getRuntime().gc(); 161 162 // Thirdly, write header, mapList. Calculate and write patched dex's sign and checksum. 163 Dex.Section headerOut = this.patchedDex.openSection(patchedToc.header.off); 164 patchedToc.writeHeader(headerOut); 165 166 Dex.Section mapListOut = this.patchedDex.openSection(patchedToc.mapList.off); 167 patchedToc.writeMap(mapListOut); 168 169 this.patchedDex.writeHashes(); 170 171 // Finally, write patched dex to file. 172 this.patchedDex.writeTo(out);
每個區域的合併演算法採用二路歸併,在old dex的基礎上對元素進行刪除,增加,替換操作。
這裡的演算法和生成補丁的DexDiff是一個逆向的過程。
1 private void doFullPatch( 2 Dex.Section oldSection, 3 int oldItemCount, 4 int[] deletedIndices, 5