前言:在之前已經梳理了微信的熱修複Tinker的接入使用流程,這麼牛逼的東西勾起了我的興趣,是以走上了探究其實作原理的道路。Tinker支援Dex、資源檔案、so檔案的熱更新,此次分析過程也将一步步的從這三個方面對Tinker進行源碼解析,跟着我的梳理希望你也可以有所收獲。
Android tinker接入使用
tinker之資源更新詳解
tinker之so更新詳解
在分析之前先copy出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的一個操作序列。
接着對每個區域比較後會将比較的結果寫入檔案中,檔案格式寫在DexDataBuffer中:
private void writeResultToStream(OutputStream os) throws IOException {
DexDataBuffer buffer = new DexDataBuffer();
buffer.write(DexPatchFile.MAGIC);
buffer.writeShort(DexPatchFile.CURRENT_VERSION);
buffer.writeInt(this.patchedDexSize);
// we will return here to write firstChunkOffset later.
int posOfFirstChunkOffsetField = buffer.position();
buffer.writeInt(0);
buffer.writeInt(this.patchedStringIdsOffset);
buffer.writeInt(this.patchedTypeIdsOffset);
buffer.writeInt(this.patchedProtoIdsOffset);
buffer.writeInt(this.patchedFieldIdsOffset);
buffer.writeInt(this.patchedMethodIdsOffset);
buffer.writeInt(this.patchedClassDefsOffset);
buffer.writeInt(this.patchedMapListOffset);
buffer.writeInt(this.patchedTypeListsOffset);
buffer.writeInt(this.patchedAnnotationSetRefListItemsOffset);
buffer.writeInt(this.patchedAnnotationSetItemsOffset);
buffer.writeInt(this.patchedClassDataItemsOffset);
buffer.writeInt(this.patchedCodeItemsOffset);
buffer.writeInt(this.patchedStringDataItemsOffset);
buffer.writeInt(this.patchedDebugInfoItemsOffset);
buffer.writeInt(this.patchedAnnotationItemsOffset);
buffer.writeInt(this.patchedEncodedArrayItemsOffset);
buffer.writeInt(this.patchedAnnotationsDirectoryItemsOffset);
buffer.write(this.oldDex.computeSignature(false));
int firstChunkOffset = buffer.position();
buffer.position(posOfFirstChunkOffsetField);
buffer.writeInt(firstChunkOffset);
buffer.position(firstChunkOffset);
writePatchOperations(buffer, this.stringDataSectionDiffAlg.getPatchOperationList());
writePatchOperations(buffer, this.typeIdSectionDiffAlg.getPatchOperationList());
writePatchOperations(buffer, this.typeListSectionDiffAlg.getPatchOperationList());
writePatchOperations(buffer, this.protoIdSectionDiffAlg.getPatchOperationList());
writePatchOperations(buffer, this.fieldIdSectionDiffAlg.getPatchOperationList());
writePatchOperations(buffer, this.methodIdSectionDiffAlg.getPatchOperationList());
writePatchOperations(buffer, this.annotationSectionDiffAlg.getPatchOperationList());
writePatchOperations(buffer, this.annotationSetSectionDiffAlg.getPatchOperationList());
writePatchOperations(buffer, this.annotationSetRefListSectionDiffAlg.getPatchOperationList());
writePatchOperations(buffer, this.annotationsDirectorySectionDiffAlg.getPatchOperationList());
writePatchOperations(buffer, this.debugInfoSectionDiffAlg.getPatchOperationList());
writePatchOperations(buffer, this.codeSectionDiffAlg.getPatchOperationList());
writePatchOperations(buffer, this.classDataSectionDiffAlg.getPatchOperationList());
writePatchOperations(buffer, this.encodedArraySectionDiffAlg.getPatchOperationList());
writePatchOperations(buffer, this.classDefSectionDiffAlg.getPatchOperationList());
byte[] bufferData = buffer.array();
os.write(bufferData);
os.flush();
}
生成的檔案以dex結尾,但需要注意的是,它不是真正的dex檔案,其格式可參考DexDataBuffer類。
二、更新檔包下發成功後合成全量Dex流程
當app收到伺服器下發的更新檔後,會觸發DefaultPatchListener.onPatchReceived事件,調用TinkerPatchService.runPatchService啟動patch程序進行更新檔patch工作。UpgradePatch.tryPatch()中會首先檢查更新檔的合法性,簽名,以及是否安裝過更新檔,檢查通過後會嘗試dex,so以及res檔案的patch。
在後面主要分析DexDiffPatchInternal.tryRecoverDexFiles,讨論dex的patch過程。
在tryRecoverDexFiles中調用DexDiffPatchInternal.patchDexFile,最終通過DexPatchApplier.executeAndSaveTo進行執行及生産全量dex。
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 {
oldDexStream = baseApk.getInputStream(oldDexEntry);
patchFileStream = (patchFileEntry != null ? patchPkg.getInputStream(patchFileEntry) : null);
final boolean isRawDexFile = SharePatchFileUtil.isRawDexFile(patchInfo.rawName);
if (!isRawDexFile || patchInfo.isJarMode) {
ZipOutputStream zos = null;
try {
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, (int) entry.getSize(), patchFileStream).executeAndSaveTo(zos);
} finally {
SharePatchFileUtil.closeQuietly(zis);
}
} else {
new DexPatchApplier(oldDexStream, (int) oldDexEntry.getSize(), patchFileStream).executeAndSaveTo(zos);
}
zos.closeEntry();
} finally {
SharePatchFileUtil.closeQuietly(zos);
}
} else {
new DexPatchApplier(oldDexStream, (int) oldDexEntry.getSize(), patchFileStream).executeAndSaveTo(patchedDexFile);
}
} finally {
SharePatchFileUtil.closeQuietly(oldDexStream);
SharePatchFileUtil.closeQuietly(patchFileStream);
}
}
DexPatchApplier.executeAndSaveTo(OutputStream out)中會對15個dex區域進行patch操作,針對old dex和patch dex進行合并,生成全量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.
// 首先old apk的簽名和patchfile所攜帶的old apk簽名是否一緻,不一緻則抛出異常
byte[] oldDexSign = this.oldDex.computeSignature(false);
if (oldDexSign == null) {
throw new IOException("failed to compute old dex's signature.");
}
if (this.patchFile != 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)
)
);
}
}
String oldDexSignStr = Hex.toHexString(oldDexSign);
// Firstly, set sections' offset after patched, sort according to their offset so that
// the dex lib of aosp can calculate section size.
// patchedDex是最終合成的dex,首先設定各個區域的偏移量
TableOfContents patchedToc = this.patchedDex.getTableOfContents();
patchedToc.header.off = 0;
patchedToc.header.size = 1;
patchedToc.mapList.size = 1;
if (extraInfoFile == null || !extraInfoFile.isAffectedOldDex(this.oldDexSignStr)) {
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();
} else {
...
}
Arrays.sort(patchedToc.sections);
patchedToc.computeSizesFromOffsets();
// Secondly, run patch algorithms according to sections' dependencies.
// 對每個區域進行patch操作
this.stringDataSectionPatchAlg = new StringDataSectionPatchAlgorithm(
patchFile, oldDex, patchedDex, oldToFullPatchedIndexMap,
patchedToSmallPatchedIndexMap, extraInfoFile
);
this.typeIdSectionPatchAlg = new TypeIdSectionPatchAlgorithm(
patchFile, oldDex, patchedDex, oldToFullPatchedIndexMap,
patchedToSmallPatchedIndexMap, extraInfoFile
);
this.protoIdSectionPatchAlg = new ProtoIdSectionPatchAlgorithm(
patchFile, oldDex, patchedDex, oldToFullPatchedIndexMap,
patchedToSmallPatchedIndexMap, extraInfoFile
);
this.fieldIdSectionPatchAlg = new FieldIdSectionPatchAlgorithm(
patchFile, oldDex, patchedDex, oldToFullPatchedIndexMap,
patchedToSmallPatchedIndexMap, extraInfoFile
);
this.methodIdSectionPatchAlg = new MethodIdSectionPatchAlgorithm(
patchFile, oldDex, patchedDex, oldToFullPatchedIndexMap,
patchedToSmallPatchedIndexMap, extraInfoFile
);
this.classDefSectionPatchAlg = new ClassDefSectionPatchAlgorithm(
patchFile, oldDex, patchedDex, oldToFullPatchedIndexMap,
patchedToSmallPatchedIndexMap, extraInfoFile
);
this.typeListSectionPatchAlg = new TypeListSectionPatchAlgorithm(
patchFile, oldDex, patchedDex, oldToFullPatchedIndexMap,
patchedToSmallPatchedIndexMap, extraInfoFile
);
this.annotationSetRefListSectionPatchAlg = new AnnotationSetRefListSectionPatchAlgorithm(
patchFile, oldDex, patchedDex, oldToFullPatchedIndexMap,
patchedToSmallPatchedIndexMap, extraInfoFile
);
this.annotationSetSectionPatchAlg = new AnnotationSetSectionPatchAlgorithm(
patchFile, oldDex, patchedDex, oldToFullPatchedIndexMap,
patchedToSmallPatchedIndexMap, extraInfoFile
);
this.classDataSectionPatchAlg = new ClassDataSectionPatchAlgorithm(
patchFile, oldDex, patchedDex, oldToFullPatchedIndexMap,
patchedToSmallPatchedIndexMap, extraInfoFile
);
this.codeSectionPatchAlg = new CodeSectionPatchAlgorithm(
patchFile, oldDex, patchedDex, oldToFullPatchedIndexMap,
patchedToSmallPatchedIndexMap, extraInfoFile
);
this.debugInfoSectionPatchAlg = new DebugInfoItemSectionPatchAlgorithm(
patchFile, oldDex, patchedDex, oldToFullPatchedIndexMap,
patchedToSmallPatchedIndexMap, extraInfoFile
);
this.annotationSectionPatchAlg = new AnnotationSectionPatchAlgorithm(
patchFile, oldDex, patchedDex, oldToFullPatchedIndexMap,
patchedToSmallPatchedIndexMap, extraInfoFile
);
this.encodedArraySectionPatchAlg = new StaticValueSectionPatchAlgorithm(
patchFile, oldDex, patchedDex, oldToFullPatchedIndexMap,
patchedToSmallPatchedIndexMap, extraInfoFile
);
this.annotationsDirectorySectionPatchAlg = new AnnotationsDirectorySectionPatchAlgorithm(
patchFile, oldDex, patchedDex, oldToFullPatchedIndexMap,
patchedToSmallPatchedIndexMap, extraInfoFile
);
this.stringDataSectionPatchAlg.execute();
this.typeIdSectionPatchAlg.execute();
this.typeListSectionPatchAlg.execute();
this.protoIdSectionPatchAlg.execute();
this.fieldIdSectionPatchAlg.execute();
this.methodIdSectionPatchAlg.execute();
Runtime.getRuntime().gc();
this.annotationSectionPatchAlg.execute();
this.annotationSetSectionPatchAlg.execute();
this.annotationSetRefListSectionPatchAlg.execute();
this.annotationsDirectorySectionPatchAlg.execute();
Runtime.getRuntime().gc();
this.debugInfoSectionPatchAlg.execute();
this.codeSectionPatchAlg.execute();
Runtime.getRuntime().gc();
this.classDataSectionPatchAlg.execute();
this.encodedArraySectionPatchAlg.execute();
this.classDefSectionPatchAlg.execute();
Runtime.getRuntime().gc();
// 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);
每個區域的合并算法采用二路歸并,在old dex的基礎上對元素進行删除,增加,替換操作。
這裡的算法和生成更新檔的DexDiff是一個逆向的過程。
private void doFullPatch(
Dex.Section oldSection,
int oldItemCount,
int[] deletedIndices,
int[] addedIndices,
int[] replacedIndices
) {
int deletedItemCount = deletedIndices.length;
int addedItemCount = addedIndices.length;
int replacedItemCount = replacedIndices.length;
int newItemCount = oldItemCount + addedItemCount - deletedItemCount;
int deletedItemCounter = 0;
int addActionCursor = 0;
int replaceActionCursor = 0;
int oldIndex = 0;
int patchedIndex = 0;
while (oldIndex < oldItemCount || patchedIndex < newItemCount) {
if (addActionCursor < addedItemCount && addedIndices[addActionCursor] == patchedIndex) {
T addedItem = nextItem(patchFile.getBuffer());
int patchedOffset = writePatchedItem(addedItem);
++addActionCursor;
++patchedIndex;
} else
if (replaceActionCursor < replacedItemCount && replacedIndices[replaceActionCursor] == patchedIndex) {
T replacedItem = nextItem(patchFile.getBuffer());
int patchedOffset = writePatchedItem(replacedItem);
++replaceActionCursor;
++patchedIndex;
} else
if (Arrays.binarySearch(deletedIndices, oldIndex) >= 0) {
T skippedOldItem = nextItem(oldSection); // skip old item.
markDeletedIndexOrOffset(
oldToFullPatchedIndexMap,
oldIndex,
getItemOffsetOrIndex(oldIndex, skippedOldItem)
);
++oldIndex;
++deletedItemCounter;
} else
if (Arrays.binarySearch(replacedIndices, oldIndex) >= 0) {
T skippedOldItem = nextItem(oldSection); // skip old item.
markDeletedIndexOrOffset(
oldToFullPatchedIndexMap,
oldIndex,
getItemOffsetOrIndex(oldIndex, skippedOldItem)
);
++oldIndex;
} else
if (oldIndex < oldItemCount) {
T oldItem = adjustItem(this.oldToFullPatchedIndexMap, nextItem(oldSection));
int patchedOffset = writePatchedItem(oldItem);
updateIndexOrOffset(
this.oldToFullPatchedIndexMap,
oldIndex,
getItemOffsetOrIndex(oldIndex, oldItem),
patchedIndex,
patchedOffset
);
++oldIndex;
++patchedIndex;
}
}
if (addActionCursor != addedItemCount || deletedItemCounter != deletedItemCount
|| replaceActionCursor != replacedItemCount
) {
throw new IllegalStateException(
String.format(
"bad patch operation sequence. addCounter: %d, addCount: %d, "
+ "delCounter: %d, delCount: %d, "
+ "replaceCounter: %d, replaceCount:%d",
addActionCursor,
addedItemCount,
deletedItemCounter,
deletedItemCount,
replaceActionCursor,
replacedItemCount
)
);
}
}
在extractDexDiffInternals調用完以後,會調用TinkerParallelDexOptimizer.optimizeAll對生成的全量dex進行optimize操作,生成odex檔案。最終合成的檔案會放到/data/data/${package_name}/tinker目錄下。
到此,生成Dex過程完成。
三、加載全量Dex流程
TinkerApplication通過反射的方式将實際的app業務隔離,這樣可以在熱更新的時候修改實際的app内容。
在TinkerApplication中的onBaseContextAttached中會通過反射調用TinkerLoader的tryLoad加載已經合成的dex。
private static final String TINKER_LOADER_METHOD = "tryLoad";
private void loadTinker() {
//disable tinker, not need to install
if (tinkerFlags == TINKER_DISABLE) {
return;
}
tinkerResultIntent = new Intent();
try {
//reflect tinker loader, because loaderClass may be define by user!
Class<?> tinkerLoadClass = Class.forName(loaderClassName, false, getClassLoader());
Method loadMethod = tinkerLoadClass.getMethod(TINKER_LOADER_METHOD, TinkerApplication.class, int.class, boolean.class);
Constructor<?> constructor = tinkerLoadClass.getConstructor();
tinkerResultIntent = (Intent) loadMethod.invoke(constructor.newInstance(), this, tinkerFlags, tinkerLoadVerifyFlag);
} catch (Throwable e) {
//has exception, put exception error code
ShareIntentUtil.setIntentReturnCode(tinkerResultIntent, ShareConstants.ERROR_LOAD_PATCH_UNKNOWN_EXCEPTION);
tinkerResultIntent.putExtra(INTENT_PATCH_EXCEPTION, e);
}
}
tryLoadPatchFilesInternal是加載Patch檔案的核心函數,主要做了以下的事情:
- tinkerFlag是否開啟,否則不加載;
- tinker目錄是否生成,沒有則表示沒有生成全量的dex,不需要重新加載;
- tinker/patch.info是否存在,否則不加載;
- 讀取patch.info,讀取失敗則不加載;
- 比較patchInfo的新舊版本,都為空則不加載;
- 判斷版本号是否為空,為空則不加載;
- 判斷patch version directory(//tinker/patch.info/patch-641e634c)是否存在;
- 判斷patchVersionDirectoryFile(//tinker/patch.info/patch-641e634c/patch-641e634c.apk)是否存在;
- checkTinkerPackage,(如tinkerId和oldTinkerId不能相等,否則不加載);
- 檢測dex的完整性,包括dex是否全部生産,是否對dex做了優化,優化後的檔案是否存在(//tinker/patch.info/patch-641e634c/dex);
- 同樣對so res檔案進行完整性檢測;
- 嘗試超過3次不加載;
- loadTinkerJars/loadTinkerResources/;
在TinkerDexLoader.loadTinkerJars處理加載dex檔案。
// 擷取PatchClassLoader
PathClassLoader classLoader = (PathClassLoader) TinkerDexLoader.class.getClassLoader();
...
// 生産合法檔案清單
ArrayList<File> legalFiles = new ArrayList<>();
final boolean isArtPlatForm = ShareTinkerInternals.isVmArt();
for (ShareDexDiffPatchInfo info : dexList) {
//for dalvik, ignore art support dex
// dalvik虛拟機中,忽略掉隻支援art的dex
if (isJustArtSupportDex(info)) {
continue;
}
String path = dexPath + info.realName;
File file = new File(path);
if (tinkerLoadVerifyFlag) {
long start = System.currentTimeMillis();
String checkMd5 = isArtPlatForm ? info.destMd5InArt : info.destMd5InDvm;
if (!SharePatchFileUtil.verifyDexFileMd5(file, checkMd5)) {
//it is good to delete the mismatch file
ShareIntentUtil.setIntentReturnCode(intentResult, ShareConstants.ERROR_LOAD_PATCH_VERSION_DEX_MD5_MISMATCH);
intentResult.putExtra(ShareIntentUtil.INTENT_PATCH_MISMATCH_DEX_PATH,
file.getAbsolutePath());
return false;
}
Log.i(TAG, "verify dex file:" + file.getPath() + " md5, use time: " + (System.currentTimeMillis() - start));
}
legalFiles.add(file);
}
// 如果系統OTA,對這些合法dex進行優化
if (isSystemOTA) {
parallelOTAResult = true;
parallelOTAThrowable = null;
Log.w(TAG, "systemOTA, try parallel oat dexes!!!!!");
TinkerParallelDexOptimizer.optimizeAll(
legalFiles, optimizeDir,
new TinkerParallelDexOptimizer.ResultCallback() {
@Override
public void onSuccess(File dexFile, File optimizedDir) {
// Do nothing.
}
@Override
public void onFailed(File dexFile, File optimizedDir, Throwable thr) {
parallelOTAResult = false;
parallelOTAThrowable = thr;
}
}
);
if (!parallelOTAResult) {
Log.e(TAG, "parallel oat dexes failed");
intentResult.putExtra(ShareIntentUtil.INTENT_PATCH_EXCEPTION, parallelOTAThrowable);
ShareIntentUtil.setIntentReturnCode(intentResult, ShareConstants.ERROR_LOAD_PATCH_VERSION_PARALLEL_DEX_OPT_EXCEPTION);
return false;
}
}
// 加載Dex
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該字段判斷是否加載成功。
public static void installDexes(Application application, PathClassLoader loader, File dexOptDir, List<File> files) throws Throwable {
if (!files.isEmpty()) {
ClassLoader classLoader = loader;
if (Build.VERSION.SDK_INT >= 24) {
classLoader = AndroidNClassLoader.inject(loader, application);
}
//because in dalvik, if inner class is not the same classloader with it wrapper class.
//it won't fail at dex2opt
if (Build.VERSION.SDK_INT >= 23) {
V23.install(classLoader, files, dexOptDir);
} else if (Build.VERSION.SDK_INT >= 19) {
V19.install(classLoader, files, dexOptDir);
} else if (Build.VERSION.SDK_INT >= 14) {
V14.install(classLoader, files, dexOptDir);
} else {
V4.install(classLoader, files, dexOptDir);
}
//install done
sPatchDexCount = files.size();
// Tinker在生成更新檔階段會生成一個test.dex,這個test.dex的作用就是用來驗證dex的加載是否成功。test.dex中含有com.tencent.tinker.loader.TinkerTestDexLoad類,該類中包含一個字段isPatch,checkDexInstall就是通過findField該字段判斷是否加載成功。
if (!checkDexInstall(classLoader)) {
//reset patch dex
SystemClassLoaderAdder.uninstallPatchDex(classLoader);
throw new TinkerRuntimeException(ShareConstants.CHECK_DEX_INSTALL_FAIL);
}
}
}
此處插入額外内容===
關于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.
*/
到這裡,大家隻需要明白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檔案繼續查找。
===插入結束
install的做法就是,先擷取BaseDexClassLoader的dexPathList對象,然後通過dexPathList的makeDexElements函數将我們要安裝的dex轉化成Element[]對象,最後将其和dexPathList的dexElements對象進行合并,就是新的Element[]對象,因為我們添加的dex都被放在dexElements數組的最前面,是以當通過findClass來查找這個類時,就是使用的我們最新的dex裡面的類。
以V19的install為例,下面的代碼非常清晰的描述了實際的加載所做的事情:
private static final class V19 {
private static void install(ClassLoader loader, List<File> additionalClassPathEntries,
File optimizedDirectory)
throws IllegalArgumentException, IllegalAccessException,
NoSuchFieldException, InvocationTargetException, NoSuchMethodException, IOException {
/* The patched class loader is expected to be a descendant of
* dalvik.system.BaseDexClassLoader. We modify its
* dalvik.system.DexPathList pathList field to append additional DEX
* file entries.
*/
Field pathListField = ShareReflectUtil.findField(loader, "pathList");
Object dexPathList = pathListField.get(loader);
ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>();
ShareReflectUtil.expandFieldArray(dexPathList, "dexElements", makeDexElements(dexPathList,
new ArrayList<File>(additionalClassPathEntries), optimizedDirectory,
suppressedExceptions));
if (suppressedExceptions.size() > 0) {
for (IOException e : suppressedExceptions) {
Log.w(TAG, "Exception in makeDexElement", e);
throw e;
}
}
}
}
因為android版本更新較快,不同版本裡面的DexPathList等類的函數和字段都有一些變化,這也是在install的時候需要對不同版本進行适配的原因。到此,在目前app的classloader裡面就包含了我們第二步驟裡面合成的全量DEX,我們在加載類的時候就能用到新的内容了。
Dex的加載流程完成。
随後我将對Tinker中對資源檔案的熱更新繼續進行分析。