編輯:關於android開發
Tinker是微信的第一個開源項目,主要用於安卓應用bug的熱修復和功能的迭代。
Tinker github地址:https://github.com/Tencent/tinker
首先向微信致敬,感謝毫無保留的開源出了這麼一款優秀的熱更新項目。
因Tinker支持Dex,資源文件及so文件的熱更新,本系列將從以下三個方面對Tinker進行源碼解析:
Tinker中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區域的比較算法:
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類。
當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 int[] addedIndices,
6 int[] replacedIndices
7 ) {
8 int deletedItemCount = deletedIndices.length;
9 int addedItemCount = addedIndices.length;
10 int replacedItemCount = replacedIndices.length;
11 int newItemCount = oldItemCount + addedItemCount - deletedItemCount;
12
13 int deletedItemCounter = 0;
14 int addActionCursor = 0;
15 int replaceActionCursor = 0;
16
17 int oldIndex = 0;
18 int patchedIndex = 0;
19 while (oldIndex < oldItemCount || patchedIndex < newItemCount) {
20 if (addActionCursor < addedItemCount && addedIndices[addActionCursor] == patchedIndex) {
21 T addedItem = nextItem(patchFile.getBuffer());
22 int patchedOffset = writePatchedItem(addedItem);
23 ++addActionCursor;
24 ++patchedIndex;
25 } else
26 if (replaceActionCursor < replacedItemCount && replacedIndices[replaceActionCursor] == patchedIndex) {
27 T replacedItem = nextItem(patchFile.getBuffer());
28 int patchedOffset = writePatchedItem(replacedItem);
29 ++replaceActionCursor;
30 ++patchedIndex;
31 } else
32 if (Arrays.binarySearch(deletedIndices, oldIndex) >= 0) {
33 T skippedOldItem = nextItem(oldSection); // skip old item.
34 markDeletedIndexOrOffset(
35 oldToFullPatchedIndexMap,
36 oldIndex,
37 getItemOffsetOrIndex(oldIndex, skippedOldItem)
38 );
39 ++oldIndex;
40 ++deletedItemCounter;
41 } else
42 if (Arrays.binarySearch(replacedIndices, oldIndex) >= 0) {
43 T skippedOldItem = nextItem(oldSection); // skip old item.
44 markDeletedIndexOrOffset(
45 oldToFullPatchedIndexMap,
46 oldIndex,
47 getItemOffsetOrIndex(oldIndex, skippedOldItem)
48 );
49 ++oldIndex;
50 } else
51 if (oldIndex < oldItemCount) {
52 T oldItem = adjustItem(this.oldToFullPatchedIndexMap, nextItem(oldSection));
53
54 int patchedOffset = writePatchedItem(oldItem);
55
56 updateIndexOrOffset(
57 this.oldToFullPatchedIndexMap,
58 oldIndex,
59 getItemOffsetOrIndex(oldIndex, oldItem),
60 patchedIndex,
61 patchedOffset
62 );
63
64 ++oldIndex;
65 ++patchedIndex;
66 }
67 }
68
69 if (addActionCursor != addedItemCount || deletedItemCounter != deletedItemCount
70 || replaceActionCursor != replacedItemCount
71 ) {
72 throw new IllegalStateException(
73 String.format(
74 "bad patch operation sequence. addCounter: %d, addCount: %d, "
75 + "delCounter: %d, delCount: %d, "
76 + "replaceCounter: %d, replaceCount:%d",
77 addActionCursor,
78 addedItemCount,
79 deletedItemCounter,
80 deletedItemCount,
81 replaceActionCursor,
82 replacedItemCount
83 )
84 );
85 }
86 }
在extractDexDiffInternals調用完以後,
會調用TinkerParallelDexOptimizer.optimizeAll對生成的全量dex進行optimize操作,生成odex文件。
最終合成的文件會放到/data/data/${package_name}/tinker目錄下。
到此,生成Dex過程完成。
TinkerApplication通過反射的方式將實際的app業務隔離,這樣可以在熱更新的時候修改實際的app內容。
在TinkerApplication中的onBaseContextAttached中會通過反射調用TinkerLoader的tryLoad加載已經合成的dex。
1 private static final String TINKER_LOADER_METHOD = "tryLoad";
2 private void loadTinker() {
3 //disable tinker, not need to install
4 if (tinkerFlags == TINKER_DISABLE) {
5 return;
6 }
7 tinkerResultIntent = new Intent();
8 try {
9 //reflect tinker loader, because loaderClass may be define by user!
10 Class<?> tinkerLoadClass = Class.forName(loaderClassName, false, getClassLoader());
11
12 Method loadMethod = tinkerLoadClass.getMethod(TINKER_LOADER_METHOD, TinkerApplication.class, int.class, boolean.class);
13 Constructor<?> constructor = tinkerLoadClass.getConstructor();
14 tinkerResultIntent = (Intent) loadMethod.invoke(constructor.newInstance(), this, tinkerFlags, tinkerLoadVerifyFlag);
15 } catch (Throwable e) {
16 //has exception, put exception error code
17 ShareIntentUtil.setIntentReturnCode(tinkerResultIntent, ShareConstants.ERROR_LOAD_PATCH_UNKNOWN_EXCEPTION);
18 tinkerResultIntent.putExtra(INTENT_PATCH_EXCEPTION, e);
19 }
20 }
tryLoadPatchFilesInternal是加載Patch文件的核心函數,主要做了以下的事情:
TinkerDexLoader.loadTinkerJars處理加載dex文件。
1 // 獲取PatchClassLoader
2 PathClassLoader classLoader = (PathClassLoader) TinkerDexLoader.class.getClassLoader();
3
4 ...
5 // 生產合法文件列表
6 ArrayList<File> legalFiles = new ArrayList<>();
7
8 final boolean isArtPlatForm = ShareTinkerInternals.isVmArt();
9 for (ShareDexDiffPatchInfo info : dexList) {
10 //for dalvik, ignore art support dex
11 // dalvik虛擬機中,忽略掉只支持art的dex
12 if (isJustArtSupportDex(info)) {
13 continue;
14 }
15 String path = dexPath + info.realName;
16 File file = new File(path);
17
18 if (tinkerLoadVerifyFlag) {
19 long start = System.currentTimeMillis();
20 String checkMd5 = isArtPlatForm ? info.destMd5InArt : info.destMd5InDvm;
21 if (!SharePatchFileUtil.verifyDexFileMd5(file, checkMd5)) {
22 //it is good to delete the mismatch file
23 ShareIntentUtil.setIntentReturnCode(intentResult, ShareConstants.ERROR_LOAD_PATCH_VERSION_DEX_MD5_MISMATCH);
24 intentResult.putExtra(ShareIntentUtil.INTENT_PATCH_MISMATCH_DEX_PATH,
25 file.getAbsolutePath());
26 return false;
27 }
28 Log.i(TAG, "verify dex file:" + file.getPath() + " md5, use time: " + (System.currentTimeMillis() - start));
29 }
30 legalFiles.add(file);
31 }
32
33 // 如果系統OTA,對這些合法dex進行優化
34 if (isSystemOTA) {
35 parallelOTAResult = true;
36 parallelOTAThrowable = null;
37 Log.w(TAG, "systemOTA, try parallel oat dexes!!!!!");
38
39 TinkerParallelDexOptimizer.optimizeAll(
40 legalFiles, optimizeDir,
41 new TinkerParallelDexOptimizer.ResultCallback() {
42 @Override
43 public void onSuccess(File dexFile, File optimizedDir) {
44 // Do nothing.
45 }
46 @Override
47 public void onFailed(File dexFile, File optimizedDir, Throwable thr) {
48 parallelOTAResult = false;
49 parallelOTAThrowable = thr;
50 }
51 }
52 );
53 if (!parallelOTAResult) {
54 Log.e(TAG, "parallel oat dexes failed");
55 intentResult.putExtra(ShareIntentUtil.INTENT_PATCH_EXCEPTION, parallelOTAThrowable);
56 ShareIntentUtil.setIntentReturnCode(intentResult, ShareConstants.ERROR_LOAD_PATCH_VERSION_PARALLEL_DEX_OPT_EXCEPTION);
57 return false;
58 }
59 }
60
61 // 加載Dex
62 SystemClassLoaderAdder.installDexes(application, classLoader, optimizeDir, legalFiles);
SystemClassLoaderAdder.installDexes中按照安卓的版本對dex進行install,這裡應該是借鑒了MultiDex裡面的install做法。
另外Tinker在生成補丁階段會生成一個test.dex,這個test.dex的作用就是用來驗證dex的加載是否成功。
test.dex中含有com.tencent.tinker.loader.TinkerTestDexLoad類,該類中包含一個字段isPatch,checkDexInstall就是通過findField該字段判斷是否加載成功。
1 public static void installDexes(Application application, PathClassLoader loader, File dexOptDir, List<File> files) throws Throwable {
2 if (!files.isEmpty()) {
3 ClassLoader classLoader = loader;
4 if (Build.VERSION.SDK_INT >= 24) {
5 classLoader = AndroidNClassLoader.inject(loader, application);
6 }
7 //because in dalvik, if inner class is not the same classloader with it wrapper class.
8 //it won't fail at dex2opt
9 if (Build.VERSION.SDK_INT >= 23) {
10 V23.install(classLoader, files, dexOptDir);
11 } else if (Build.VERSION.SDK_INT >= 19) {
12 V19.install(classLoader, files, dexOptDir);
13 } else if (Build.VERSION.SDK_INT >= 14) {
14 V14.install(classLoader, files, dexOptDir);
15 } else {
16 V4.install(classLoader, files, dexOptDir);
17 }
18 //install done
19 sPatchDexCount = files.size();
20
21 // Tinker在生成補丁階段會生成一個test.dex,這個test.dex的作用就是用來驗證dex的加載是否成功。test.dex中含有com.tencent.tinker.loader.TinkerTestDexLoad類,該類中包含一個字段isPatch,checkDexInstall就是通過findField該字段判斷是否加載成功。
22 if (!checkDexInstall(classLoader)) {
23 //reset patch dex
24 SystemClassLoaderAdder.uninstallPatchDex(classLoader);
25 throw new TinkerRuntimeException(ShareConstants.CHECK_DEX_INSTALL_FAIL);
26 }
27 }
28 }
------分割線-----
在講install具體細節之前,回顧一下具體原理。關於Android的ClassLoader體系,android中加載類一般使用的是PathClassLoader和DexClassLoader
PathClassLoader,源碼注釋可以看出,android使用這個類作為系統類和應用類的加載器。
/**
* Provides a simple {@link ClassLoader} implementation that operates on a list
* of files and directories in the local file system, but does not attempt to
* load classes from the network. Android uses this class for its system class
* loader and for its application class loader(s).
*/
DexClassLoader,源碼注釋可以看出,可以用來從.jar和.apk類型的文件內部加載classes.dex文件。
/**
* A class loader that loads classes from {@code .jar} and {@code .apk} files
* containing a {@code classes.dex} entry. This can be used to execute code not
* installed as part of an application.
*
* <p>This class loader requires an application-private, writable directory to
* cache optimized classes. Use {@code Context.getDir(String, int)} to create
* such a directory: <pre> {@code
* File dexOutputDir = context.getDir("dex", 0);
* }</pre>
*
* <p><strong>Do not cache optimized classes on external storage.</strong>
* External storage does not provide access controls necessary to protect your
* application from code injection attacks.
*/
ok,到這裡,大家只需要明白,Android使用PathClassLoader作為其類加載器,DexClassLoader可以從.jar和.apk類型的文件內部加載classes.dex文件就好了。
PathClassLoader和DexClassLoader都繼承自BaseDexClassLoader。在BaseDexClassLoader中有如下源碼:
##BaseDexClassLoader.java##
/** structured lists of path elements */
private final DexPathList pathList;
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
Class clazz = pathList.findClass(name);
if (clazz == null) {
throw new ClassNotFoundException(name);
}
return clazz;
}
##DexPathList.java##
/** list of dex/resource (class path) elements */
private final Element[] dexElements;
public Class findClass(String name) {
for (Element element : dexElements) {
DexFile dex = element.dexFile;
if (dex != null) {
Class clazz = dex.loadClassBinaryName(name, definingContext);
if (clazz != null) {
return clazz;
}
}
}
return null;
}
##DexFile.java##
public Class loadClassBinaryName(String name, ClassLoader loader) {
return defineClass(name, loader, mCookie);
}
private native static Class defineClass(String name, ClassLoader loader, int cookie);
通俗點講:
一個ClassLoader可以包含多個dex文件,每個dex文件是一個Element,多個dex文件排列成一個有序的數組dexElements,當找類的時候,會按順序遍歷dex文件,然後從當前遍歷的dex文件中找類,如果找類則返回,如果找不到從下一個dex文件繼續查找。(來自:安卓App熱補丁動態修復技術介紹)
回到分割線以前:
install的做法就是,先獲取BaseDexClassLoader的dexPathList對象,
然後通過dexPathList的makeDexElements函數將我們要安裝的dex轉化成Element[]對象,
最後將其和dexPathList的dexElements對象進行合並,就是新的Element[]對象,
因為我們添加的dex都被放在dexElements數組的最前面,所以當通過findClass來查找這個類時,就是使用的我們最新的dex裡面的類。
以V19的install為例,下面的代碼非常清晰的描述了實際的加載所做的事情:
1 private static final class V19 {
2 private static void install(ClassLoader loader, List<File> additionalClassPathEntries,
3 File optimizedDirectory)
4 throws IllegalArgumentException, IllegalAccessException,
5 NoSuchFieldException, InvocationTargetException, NoSuchMethodException, IOException {
6 /* The patched class loader is expected to be a descendant of
7 * dalvik.system.BaseDexClassLoader. We modify its
8 * dalvik.system.DexPathList pathList field to append additional DEX
9 * file entries.
10 */
11 Field pathListField = ShareReflectUtil.findField(loader, "pathList");
12 Object dexPathList = pathListField.get(loader);
13 ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>();
14 ShareReflectUtil.expandFieldArray(dexPathList, "dexElements", makeDexElements(dexPathList,
15 new ArrayList<File>(additionalClassPathEntries), optimizedDirectory,
16 suppressedExceptions));
17 if (suppressedExceptions.size() > 0) {
18 for (IOException e : suppressedExceptions) {
19 Log.w(TAG, "Exception in makeDexElement", e);
20 throw e;
21 }
22 }
23 }
24 }
因為android版本更新較快,不同版本裡面的DexPathList等類的函數和字段都有一些變化,這也是在install的時候需要對不同版本進行適配的原因。
到此,在當前app的classloader裡面就包含了我們第二步驟裡面合成的全量DEX,我們在加載類的時候就能用到新的內容了。
Congratulations!!Dex的加載流程完成。
轉載請標明本文來源:http://www.cnblogs.com/yyangblog/p/6249715.html
更多內容歡迎star作者的github:https://github.com/LaurenceYang/article
如果發現本文有什麼問題和任何建議,也隨時歡迎交流~
下一篇文章我們將對Tinker中對資源文件的熱更新進行分析。
Android應用開發教程之五:EditText詳解
EditText在API中的結構 java.lang.Object android.view.View android.widget.Text
ListView的CheckBox實現全部選中/不選中,listviewcheckbox
ListView的CheckBox實現全部選中/不選中,listviewcheckbox在Adapter類中定義一個HashMap列表,保存每一行是否被選中: priv
Java 單例模式,Java模式
Java 單例模式,Java模式前言:昨天公司計劃把項目中的部分功能做出SDK的形式,供其他公司的產品使用,所以不得不重新研究一下單例模式。 為什麼單例 1
Android數據庫相關整理,android數據庫整理
Android數據庫相關整理,android數據庫整理今天對Android中數據庫相關的操作和代碼做了一個整理,便於自己之後的查閱。主要內容有: 1.原生數據庫寫法 2.