前言
在 Tinker學(xué)習(xí)計(jì)劃(1)-Tinker的集成 這邊文章中我們首先學(xué)習(xí)了如何去集成Tinker熱更新框架,去實(shí)現(xiàn)我們自己App的熱更新功能。這篇文章主要是從架構(gòu)和源碼的角度去理解Tinker。我計(jì)劃是分成以下幾步:
- Tinker的結(jié)構(gòu)(主要是分析Tinker的架構(gòu),從宏觀(guān)層面來(lái)了解Tinker)
- Tinker代碼修復(fù)的原理(描述補(bǔ)丁包生效的過(guò)程,源碼分析)
- Tinker資源修復(fù)的原理(Tinker資源構(gòu)建的原理,及修復(fù)的原理,源碼層面)
- Tinker對(duì).SO是如何處理的(源碼層面)
- tinker-patch-gradle-plugin源碼解析
- DexDiff算法(源碼層面)
Tinker的結(jié)構(gòu)
包結(jié)構(gòu)
在 Tinker的集成 這篇文章,我們的思路基本是按照Tinker Demo里提供的思路來(lái)集成,說(shuō)白了就是通過(guò)在Gradle里Compile騰訊上傳的代碼,這的確能達(dá)成目的,但是對(duì)于學(xué)習(xí)卻是不方便的,雖然能看到源碼,但是.class文件在IDE里操作起來(lái)還是很麻煩的,所以在分析結(jié)構(gòu)和學(xué)習(xí)源碼之前,我們重新打個(gè)工程,這個(gè)工程里不會(huì)像下面這樣來(lái)應(yīng)用Lib了:
compile('com.tencent.tinker:tinker-android-lib:1.7.11')
provided("com.tencent.tinker:tinker-android-anno:1.7.11")
而是直接把Tinker開(kāi)源的其他代碼來(lái)搭建一個(gè)包含所有源碼的工程,講起來(lái)特別高大上實(shí)際實(shí)現(xiàn)起來(lái)很簡(jiǎn)單,這里就不細(xì)講了。貼幾張圖查來(lái)大家就了解了。
首先你注釋掉 app/build.gradle 中上面的兩個(gè)依賴(lài)庫(kù)。然后拷貝Github上Tinker的庫(kù)過(guò)來(lái)。
下面是整個(gè)工程的結(jié)構(gòu):

settings.gradle,修改成如下即可:

同步時(shí),可能會(huì)遇到如下問(wèn)題:
Error:Unable to find method 'org.gradle.api.internal.artifacts.configurations.ConfigurationInternal.getModule()Lorg/gradle/api/internal/artifacts/ModuleInternal;'.
Possible causes for this unexpected error include:<ul><li>Gradle's dependency cache may be corrupt (this sometimes occurs after a network connection timeout.)
<a href="syncProject">Re-download dependencies and sync project (requires network)</a></li><li>The state of a Gradle build process (daemon) may be corrupt. Stopping all Gradle daemons may solve this problem.
<a href="stopGradleDaemons">Stop Gradle build processes (requires restart)</a></li><li>Your project may be using a third-party plugin which is not compatible with the other plugins in the project or the version of Gradle requested by the project.</li></ul>In the case of corrupt Gradle processes, you can also try closing the IDE and then killing all Java processes.
解決該問(wèn)題:只需要修改以下兩個(gè)地方:
-
修改 /gradle/gradle-wrapper.properties 文件:
distributionUrl=https://services.gradle.org/distributions/gradle-3.3-all.zip
修改為:
distributionUrl=https://services.gradle.org/distributions/gradle-2.14.1-all.zip
-
工程根目錄 build.gradle 的gradle插件依賴(lài)修改版本,如下:
classpath 'com.android.tools.build:gradle:2.2.0'
其他的估計(jì)就是一些路徑問(wèn)題,出現(xiàn)的時(shí)候看著改下就可以了。
可能有的童鞋還是不太了解,索性簡(jiǎn)單花了一個(gè)圖來(lái)表示各個(gè)庫(kù)的依賴(lài)關(guān)系:

tinker_android_anno :
工程結(jié)構(gòu)如下:

簡(jiǎn)單的看了下,實(shí)際上這是一個(gè)為了在編譯期間生成Application的類(lèi),至于原理很簡(jiǎn)單,使用者通過(guò)DefaultLifeCycle這個(gè)注解來(lái)填充一些數(shù)據(jù),AnnotationProcessor是集成AbstractProcessor這個(gè)類(lèi)的,這個(gè)庫(kù)的作用也就是說(shuō)在編譯完成以后,自動(dòng)給工程填充了一個(gè)Application類(lèi),如下:

關(guān)于AbstractProcessor原理介紹,可參考文章:
AbstractProcessor參考
tinker-android-lib
跟Tinker框架相關(guān)的類(lèi)及代碼都是在Module中,我們業(yè)務(wù)開(kāi)發(fā)包括集成也只需要關(guān)系這個(gè)Module即可。
tinker-commons
從名稱(chēng)就可以看出該Module是業(yè)務(wù)無(wú)關(guān)的,里面主要是DexPatch相關(guān)的代碼,包括Tinker是DexDiff核心的部分,后期我們會(huì)詳細(xì)分析。
third-party:aosp_dexutils
主要包含tinker是如何定義Dex的,這個(gè)也是業(yè)務(wù)無(wú)關(guān)的
third-party:bsdiff_util
bsdiff相關(guān)的代碼,在某些情況下,微信的Dex合成實(shí)際上用的是Bsdiff算法,當(dāng)然做了優(yōu)化。
框架原理
這章我們主要對(duì)Tinker的框架進(jìn)行分析,從宏觀(guān)層面了解tinker的構(gòu)造,我盡量講的詳細(xì),這是站在我的角度來(lái)思考這個(gè)問(wèn)題,有可能和原作者思想有出入的地方,大家理解。講解之前先看一張圖:

可以看出,集成了tinker的App框架分成了兩類(lèi)進(jìn)程,一個(gè)是原來(lái)具體業(yè)務(wù)中的進(jìn)程,一個(gè)是tinker需要的Patch進(jìn)程。他們的分工也很明確,我們先說(shuō)下patch進(jìn)程,這個(gè)很好理解,類(lèi)似于我們的Demo比較簡(jiǎn)單,一個(gè)按鈕作為加載load補(bǔ)丁包的入口,或許線(xiàn)上的項(xiàng)目可能是由于服務(wù)端灰度來(lái)控制補(bǔ)丁包的下發(fā),然后再去加載,事實(shí)上都是一個(gè)意思,而這個(gè)加載補(bǔ)丁包的過(guò)程就是在patch進(jìn)程中進(jìn)行的,為什么這么做應(yīng)該也是因?yàn)闉榱藴p少業(yè)務(wù)進(jìn)程的開(kāi)銷(xiāo)吧。從圖中可以看出,patch進(jìn)程的作用主要是在合并Dex,即通過(guò)DexDiff算法來(lái)合并原始Dex和補(bǔ)丁Dex,優(yōu)化Dex及為了避免在進(jìn)程重啟時(shí)做這個(gè)事,在合并玩Dex后,即完成dexopt的過(guò)程。當(dāng)然做這些事之前都有一些校驗(yàn)的工作。至于patch上報(bào),說(shuō)白了就是定義了一個(gè)生命周期,在做這些事情當(dāng)中如果發(fā)生一些異常行為,可以上報(bào)給其他模塊或者服務(wù)端,tinker這塊是支持自定義的。按照規(guī)則實(shí)現(xiàn)它的接口即可。
具體業(yè)務(wù)進(jìn)程的定位很簡(jiǎn)單,只要是做了tinker框架的初始化,檢查是否存在補(bǔ)丁吧,如果有補(bǔ)丁包,通過(guò)hook的方式替換原始Dex的加載。校驗(yàn)?zāi)K也是必須的,load的上報(bào)和patch的上報(bào)類(lèi)似。至于里面還有一些安全模式的校驗(yàn),我們后面會(huì)在談到。
那這兩個(gè)進(jìn)程是怎么進(jìn)行協(xié)同的,實(shí)際上他們是通過(guò)文件來(lái)進(jìn)行溝通和傳遞數(shù)據(jù)的,由于涉及到多進(jìn)程來(lái)訪(fǎng)問(wèn)文件,所以里面也用到了文件鎖來(lái)避免出現(xiàn)問(wèn)題。
這個(gè)圖只是一個(gè)抽象,讓大家從上帝角度來(lái)了解tinker的工作原理,這個(gè)時(shí)候只需要知道tinker是這種原理工作的。
patch進(jìn)程工作的流程圖:

現(xiàn)在是不是有感覺(jué)了,大概知道patch進(jìn)程的工作原理了。接下來(lái),開(kāi)始擼源碼了。
TinkerInstaller.onReceiveUpgradePatch(getApplicationContext(), "/sdcard/patch.apk");
這個(gè)就是補(bǔ)丁包加載的入口,后面的路徑就是放補(bǔ)丁包的路徑,如果是云端下發(fā)的,就是下載的地址。
DefaultPatchListener.java
int returnCode = patchCheck(path);
if (returnCode == ShareConstants.ERROR_PATCH_OK) {
TinkerPatchService.runPatchService(context, path);
} else {
Tinker.with(context).getLoadReporter().onLoadPatchListenerReceiveFail(new File(path), returnCode);
}
return returnCode;
protected int patchCheck(String path) {
Tinker manager = Tinker.with(context);
//如果有設(shè)置項(xiàng)則判斷是否啟動(dòng)tinker,包括在Application中傳入的參數(shù)來(lái)決定
if (!manager.isTinkerEnabled() || !ShareTinkerInternals.isTinkerEnableWithSharedPreferences(context)) {
return ShareConstants.ERROR_PATCH_DISABLE;
}
File file = new File(path);
//判斷文件合理性
if (!SharePatchFileUtil.isLegalFile(file)) {
return ShareConstants.ERROR_PATCH_NOTEXIST;
}
//patch進(jìn)程中不處理這個(gè)操作
if (manager.isPatchProcess()) {
return ShareConstants.ERROR_PATCH_INSERVICE;
}
//由于對(duì)與一個(gè)patch操作,做完了就會(huì)kill patch進(jìn)程,如果當(dāng)前patch進(jìn)程正存在,則不處理
if(TinkerServiceInternals.isTinkerPatchServiceRunning(context)) {
return ShareConstants.ERROR_PATCH_RUNNING;
}
//不支持vm支持jit編譯以及apilevel在24以下的機(jī)器
if (ShareTinkerInternals.isVmJit()) {
return ShareConstants.ERROR_PATCH_JIT;
}
return ShareConstants.ERROR_PATCH_OK;
}
}
這個(gè)類(lèi)很簡(jiǎn)單,就是做一些基本的驗(yàn)證,如果錯(cuò)了,會(huì)丟到LoadReporter這個(gè)接口的實(shí)現(xiàn)類(lèi),至于怎么處理,用戶(hù)可以自定義。tinker的默認(rèn)實(shí)現(xiàn)只是打印了錯(cuò)誤日志而已。這里就不貼代碼了。我們按照流程接著往下說(shuō),驗(yàn)證成功以后,tinker會(huì)啟動(dòng)一個(gè)IntentService。處于 com.XXX.XXX:patch 進(jìn)程中。
public static void runPatchService(Context context, String path) {
try {
Intent intent = new Intent(context, TinkerPatchService.class);
intent.putExtra(PATCH_PATH_EXTRA, path);
intent.putExtra(RESULT_CLASS_EXTRA, resultServiceClass.getName());
context.startService(intent);
} catch (Throwable throwable) {
TinkerLog.e(TAG, "start patch service fail, exception:" + throwable);
}
}
}
我們?cè)趤?lái)看看TinkerPatchService中的OnHanderIntent這個(gè)方法。
protected void onHandleIntent(Intent intent) {
final Context context = getApplicationContext();
Tinker tinker = Tinker.with(context);
//patch開(kāi)始時(shí),上報(bào)給patchReporter的實(shí)現(xiàn)類(lèi)
tinker.getPatchReporter().onPatchServiceStart(intent);
if (intent == null) {
TinkerLog.e(TAG, "TinkerPatchService received a null intent, ignoring.");
return;
}
String path = getPatchPathExtra(intent);
if (path == null) {
TinkerLog.e(TAG, "TinkerPatchService can't get the path extra, ignoring.");
return;
}
File patchFile = new File(path);
long begin = SystemClock.elapsedRealtime();
boolean result;
long cost;
Throwable e = null;
//提升進(jìn)程優(yōu)先級(jí),盡快讓patch操作執(zhí)行,tinker用了兩種不同的方案,感
//興趣的童鞋可以看下
increasingPriority();
PatchResult patchResult = new PatchResult();
try {
if (upgradePatchProcessor == null) {
throw new TinkerRuntimeException("upgradePatchProcessor is null.");
}
result = upgradePatchProcessor.tryPatch(context, path, patchResult);
} catch (Throwable throwable) {
e = throwable;
result = false;
tinker.getPatchReporter().onPatchException(patchFile, e);
}
cost = SystemClock.elapsedRealtime() - begin;
//patch結(jié)果出來(lái)后,上報(bào)給patchReporter的實(shí)現(xiàn)類(lèi)
tinker.getPatchReporter().
onPatchResult(patchFile, result, cost);
patchResult.isSuccess = result;
patchResult.rawPatchFilePath = path;
patchResult.costTime = cost;
patchResult.e = e;
AbstractResultService.runResultService(context, patchResult, getPatchResultExtra(intent));
}
}
可以看出來(lái),這個(gè)主要是調(diào)用upgradePatchProcessor這個(gè)類(lèi)的方法tryPatch。分析這個(gè)之前,我們先聊下PatchReporter這個(gè)接口。
public interface PatchReporter {
void onPatchServiceStart(Intent intent);
void onPatchPackageCheckFail(File patchFile, int errorCode);
void onPatchVersionCheckFail(File patchFile, SharePatchInfo oldPatchInfo, String patchFileVersion);
void onPatchTypeExtractFail(File patchFile, File extractTo, String filename, int fileType);
void onPatchDexOptFail(File patchFile, List<File> dexFiles, Throwable t);
void onPatchResult(File patchFile, boolean success, long cost);
void onPatchException(File patchFile, Throwable e);
void onPatchInfoCorrupted(File patchFile, String oldVersion, String newVersion);
}
實(shí)際上細(xì)心的同學(xué)就會(huì)發(fā)現(xiàn),tinker里很多這種設(shè)計(jì),主要也是為了方便開(kāi)發(fā)者自定義一些行為,tinker封裝了最核心的那部分代碼。比方說(shuō)patch操作,如果在patch操作中出現(xiàn)一些問(wèn)題,開(kāi)發(fā)者可以定義其行為。
好,我們接著看patch的流程:
public boolean tryPatch(Context context, String tempPatchPath, PatchResult patchResult) {
Tinker manager = Tinker.with(context);
final File patchFile = new File(tempPatchPath);
if (!manager.isTinkerEnabled() || !ShareTinkerInternals.isTinkerEnableWithSharedPreferences(context)) {
TinkerLog.e(TAG, "UpgradePatch tryPatch:patch is disabled, just return");
return false;
}
if (!SharePatchFileUtil.isLegalFile(patchFile)) {
TinkerLog.e(TAG, "UpgradePatch tryPatch:patch file is not found, just return");
return false;
}
//check the signature, we should create a new checker
ShareSecurityCheck signatureCheck = new ShareSecurityCheck(context);
int returnCode = ShareTinkerInternals.checkTinkerPackage(context, manager.getTinkerFlags(), patchFile, signatureCheck);
if (returnCode != ShareConstants.ERROR_PACKAGE_CHECK_OK) {
TinkerLog.e(TAG, "UpgradePatch tryPatch:onPatchPackageCheckFail");
manager.getPatchReporter().onPatchPackageCheckFail(patchFile, returnCode);
return false;
}
String patchMd5 = SharePatchFileUtil.getMD5(patchFile);
if (patchMd5 == null) {
TinkerLog.e(TAG, "UpgradePatch tryPatch:patch md5 is null, just return");
return false;
}
//use md5 as version
patchResult.patchVersion = patchMd5;
//check ok, we can real recover a new patch
final String patchDirectory = manager.getPatchDirectory().getAbsolutePath();
File patchInfoLockFile = SharePatchFileUtil.getPatchInfoLockFile(patchDirectory);
File patchInfoFile = SharePatchFileUtil.getPatchInfoFile(patchDirectory);
SharePatchInfo oldInfo = SharePatchInfo.readAndCheckPropertyWithLock(patchInfoFile, patchInfoLockFile);
//it is a new patch, so we should not find a exist
SharePatchInfo newInfo;
//already have patch
if (oldInfo != null) {
if (oldInfo.oldVersion == null || oldInfo.newVersion == null || oldInfo.oatDir == null) {
TinkerLog.e(TAG, "UpgradePatch tryPatch:onPatchInfoCorrupted");
manager.getPatchReporter().onPatchInfoCorrupted(patchFile, oldInfo.oldVersion, oldInfo.newVersion);
return false;
}
if (!SharePatchFileUtil.checkIfMd5Valid(patchMd5)) {
TinkerLog.e(TAG, "UpgradePatch tryPatch:onPatchVersionCheckFail md5 %s is valid", patchMd5);
manager.getPatchReporter().onPatchVersionCheckFail(patchFile, oldInfo, patchMd5);
return false;
}
// if it is interpret now, use changing flag to wait main process
final String finalOatDir = oldInfo.oatDir.equals(ShareConstants.INTERPRET_DEX_OPTIMIZE_PATH)
? ShareConstants.CHANING_DEX_OPTIMIZE_PATH : oldInfo.oatDir;
newInfo = new SharePatchInfo(oldInfo.oldVersion, patchMd5, Build.FINGERPRINT, finalOatDir);
} else {
newInfo = new SharePatchInfo("", patchMd5, Build.FINGERPRINT, ShareConstants.DEFAULT_DEX_OPTIMIZE_PATH);
}
//it is a new patch, we first delete if there is any files
//don't delete dir for faster retry
// SharePatchFileUtil.deleteDir(patchVersionDirectory);
final String patchName = SharePatchFileUtil.getPatchVersionDirectory(patchMd5);
final String patchVersionDirectory = patchDirectory + "/" + patchName;
TinkerLog.i(TAG, "UpgradePatch tryPatch:patchVersionDirectory:%s", patchVersionDirectory);
//copy file
File destPatchFile = new File(patchVersionDirectory + "/" + SharePatchFileUtil.getPatchVersionFile(patchMd5));
try {
// check md5 first
if (!patchMd5.equals(SharePatchFileUtil.getMD5(destPatchFile))) {
SharePatchFileUtil.copyFileUsingStream(patchFile, destPatchFile);
TinkerLog.w(TAG, "UpgradePatch copy patch file, src file: %s size: %d, dest file: %s size:%d", patchFile.getAbsolutePath(), patchFile.length(),
destPatchFile.getAbsolutePath(), destPatchFile.length());
}
} catch (IOException e) {
// e.printStackTrace();
TinkerLog.e(TAG, "UpgradePatch tryPatch:copy patch file fail from %s to %s", patchFile.getPath(), destPatchFile.getPath());
manager.getPatchReporter().onPatchTypeExtractFail(patchFile, destPatchFile, patchFile.getName(), ShareConstants.TYPE_PATCH_FILE);
return false;
}
//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;
}
if (!BsDiffPatchInternal.tryRecoverLibraryFiles(manager, signatureCheck, context, patchVersionDirectory, destPatchFile)) {
TinkerLog.e(TAG, "UpgradePatch tryPatch:new patch recover, try patch library failed");
return false;
}
if (!ResDiffPatchInternal.tryRecoverResourceFiles(manager, signatureCheck, context, patchVersionDirectory, destPatchFile)) {
TinkerLog.e(TAG, "UpgradePatch tryPatch:new patch recover, try patch resource failed");
return false;
}
// check dex opt file at last, some phone such as VIVO/OPPO like to change dex2oat to interpreted
if (!DexDiffPatchInternal.waitAndCheckDexOptFile(patchFile, manager)) {
TinkerLog.e(TAG, "UpgradePatch tryPatch:new patch recover, check dex opt file failed");
return false;
}
if (!SharePatchInfo.rewritePatchInfoFileWithLock(patchInfoFile, newInfo, patchInfoLockFile)) {
TinkerLog.e(TAG, "UpgradePatch tryPatch:new patch recover, rewrite patch info failed");
manager.getPatchReporter().onPatchInfoCorrupted(patchFile, newInfo.oldVersion, newInfo.newVersion);
return false;
}
TinkerLog.w(TAG, "UpgradePatch tryPatch: done, it is ok");
return true;
}
這個(gè)函數(shù)體有幾個(gè)重要的步驟,一個(gè)是簽名檢查:ShareSecurityCheck。然后是MD5檢查,然后是Patch的核心部分,也就是DexDiffPatchInternal、BsDiffPatchInternal、ResDiffPatchInternal,他們都是集成BasePatchInternal。我們先看ShareSecurityCheck:
ShareSecurityCheck
他主要是做了這些工作,第一是判斷patch包的簽名和當(dāng)前安裝的apk的簽名是否一致,詳細(xì)代碼在ShareSecurityCheck類(lèi)的verifyPatchMetaSignature函數(shù)。第二就是判斷patch包的tinker_id是否和基準(zhǔn)包的tinker_id是否一致。這個(gè)很好理解,各自取出manefest中的tinker_id的值來(lái)坐下equals判斷即可。昨晚這些以后,tinker還做了一個(gè)判斷,如下:
public static int checkPackageAndTinkerFlag(ShareSecurityCheck securityCheck, int tinkerFlag) {
if (isTinkerEnabledAll(tinkerFlag)) {
return ShareConstants.ERROR_PACKAGE_CHECK_OK;
}
HashMap<String, String> metaContentMap = securityCheck.getMetaContentMap();
//check dex
boolean dexEnable = isTinkerEnabledForDex(tinkerFlag);
if (!dexEnable && metaContentMap.containsKey(ShareConstants.DEX_META_FILE)) {
return ShareConstants.ERROR_PACKAGE_CHECK_TINKERFLAG_NOT_SUPPORT;
}
//check native library
boolean nativeEnable = isTinkerEnabledForNativeLib(tinkerFlag);
if (!nativeEnable && metaContentMap.containsKey(ShareConstants.SO_META_FILE)) {
return ShareConstants.ERROR_PACKAGE_CHECK_TINKERFLAG_NOT_SUPPORT;
}
//check resource
boolean resEnable = isTinkerEnabledForResource(tinkerFlag);
if (!resEnable && metaContentMap.containsKey(ShareConstants.RES_META_FILE)) {
return ShareConstants.ERROR_PACKAGE_CHECK_TINKERFLAG_NOT_SUPPORT;
}
return ShareConstants.ERROR_PACKAGE_CHECK_OK;
}
tinkerFlag是Application申明的時(shí)候我們初始化的,主要的含義就是當(dāng)前tinker支持哪些熱更新,dex,so,res,還是全部,或者一個(gè)都不。那這個(gè)函數(shù)主要的作用的是什么呢,實(shí)際上tinker在構(gòu)建patch包時(shí),如果資源有更新,會(huì)在asset下生成一個(gè)文件dex_meta.txt。

如果有資源或者so的更新也會(huì)對(duì)應(yīng)生成,so_meta.txt和res_meta.txt。這樣就很好理解了,比如說(shuō)當(dāng)前tinker我們?cè)O(shè)置成不支持代碼更新,但是代碼確在patch包修改了,那當(dāng)前的patch包我們認(rèn)為是一個(gè)無(wú)效的patch包,隨之放棄更新。
我們接著說(shuō)MD5的校驗(yàn)、說(shuō)這個(gè)之前,我們先徹底的分析一下,tinker存儲(chǔ)patch的文件結(jié)構(gòu),下面是一個(gè)做過(guò)一次熱更新的時(shí)候的app files下文件結(jié)構(gòu)圖。

- info.lock這個(gè)文件是為了解決跨進(jìn)程讀寫(xiě)patch.info這個(gè)文件所建立的文件鎖
-
patch.info說(shuō)白了是一個(gè)配置文件,里面是處理完補(bǔ)丁包以后的一些配置信息,當(dāng)進(jìn)程重啟以后,patch以外的其他進(jìn)程會(huì)去讀這個(gè)文件數(shù)據(jù)來(lái)獲取當(dāng)前是否要加載patch的合成包,cat一下這個(gè)文件
說(shuō)白了就是一個(gè)?key-value形式的ini配置文件,上面#號(hào)開(kāi)頭的是注釋?zhuān)梢圆挥霉埽琩ir對(duì)應(yīng)的是dex目錄,print是當(dāng)前rom信息,為了判斷OTA升級(jí)用的,new和old是版本,tinker的版本是以MD5來(lái)做處理的。
- patch-xxxx 目錄下面存儲(chǔ)的是補(bǔ)丁包的具體數(shù)據(jù),patch后面那串?dāng)?shù)字實(shí)際上是補(bǔ)丁包MD5的0到8位。至于為什么是0-8位,是協(xié)議層面的事情,暫不撰述。
- patch-xxxx.apk就是補(bǔ)丁包,從云端下載復(fù)制到改目錄下的。
- dex目錄,目錄中是從patch.apk中抽取的dex文件,至于為什么以jar結(jié)尾(實(shí)際上也是dex_meta.txt這個(gè)文件中來(lái)控制的),原因暫且不明,而為什么在編譯的的時(shí)候做這個(gè)事,猜測(cè)可能是由于兼容性(davalik和art).
- odex目錄,是通過(guò)dex目錄中dex文件經(jīng)過(guò)優(yōu)化Dex2oat而來(lái)。兩種方式,一種是DexFile.loadDex(**),第二種是通過(guò)在代碼中構(gòu)建命令行來(lái)進(jìn)行優(yōu)化。
那如果是多個(gè)補(bǔ)丁包呢?
實(shí)際上就是在patch-xxxx目錄并列的層次下面多一個(gè)patch-xxxx目錄而已,后面的xxxx就是那個(gè)補(bǔ)丁包的md5取 0-8位。只不過(guò)patch.info里的old和new字段對(duì)應(yīng)的是最新的那個(gè)補(bǔ)丁包的md5值。
說(shuō)完了文件結(jié)構(gòu),我們接著上面的MD5校驗(yàn)繼續(xù)擼。
MD5在Tinker里的作用主要以下幾個(gè)方面
- 安全校驗(yàn),這個(gè)很好理解
- 作為當(dāng)前patch的版本號(hào),以及文件夾以MD5作為標(biāo)識(shí),類(lèi)似于Patch-xxxx
- 多個(gè)補(bǔ)丁包更新時(shí),判斷兩個(gè)補(bǔ)丁包是否一致
獲取MD5是通過(guò)下面這個(gè)方法來(lái)完成的:
public final static String getMD5(final InputStream is) {
if (is == null) {
return null;
}
try {
BufferedInputStream bis = new BufferedInputStream(is);
MessageDigest md = MessageDigest.getInstance("MD5");
StringBuilder md5Str = new StringBuilder(32);
byte[] buf = new byte[ShareConstants.MD5_FILE_BUF_LENGTH];
int readCount;
while ((readCount = bis.read(buf)) != -1) {
md.update(buf, 0, readCount);
}
byte[] hashValue = md.digest();
for (int i = 0; i < hashValue.length; i++) {
md5Str.append(Integer.toString((hashValue[i] & 0xff) + 0x100, 16).substring(1));
}
return md5Str.toString();
} catch (Exception e) {
return null;
}
那現(xiàn)在配置文件,源文件都準(zhǔn)備好了,就要做下面的dex合成,資源合成了。這塊我們留到下篇文章接著細(xì)說(shuō)。
總結(jié)
好的,今天的文章就到這里,主要分析了Tinker的結(jié)構(gòu),tinker的啟動(dòng)流程,以及在做dex合成之前,tinker做了哪些事情。下一節(jié)我們繼續(xù)分析。
