寫在前面的話
<p>
前面一篇Tinker相關的文章已經介紹了Tinker熱修復框架的使用與整個的修復流程,那么這一篇就要開啟Tinker的源碼解析之路了。
首先簡單說一下Tinker的原理,Tinker其實也是類似multidex的dex方式,將目標dex插入到數組最前面,主要是通過對比原dex文件(存在bug)與現dex文件(bug已修復)生成差異包,生成的差異包作為補丁包下發給客戶端,客戶端做一系列校驗之后,將下發的差異包與本應用的dex文件合并成成全量的dex文件,并進行opt優化,當再次啟動APP時候則加載優化過的全量dex文件,將dex文件插入到DexPathList 中 dexElements的前面。
所以Tinker其實是兩個流程,一個是加載補丁包,另外一個是加載dex文件,兩個的加載流程相對較長,這里分開說明,這一篇呢,主要介紹加載補丁包的流程。
補丁包加載流程
<p>
加載補丁包的方法如下
TinkerInstaller.onReceiveUpgradePatch(getApplicationContext(), "FilePath");
往下看發現調用了TinkerInstaller的onReceiveUpgradePatch方法
TinkerInstaller.java
public static void onReceiveUpgradePatch(Context context, String patchLocation) {
Tinker.with(context).getPatchListener().onPatchReceived(patchLocation);
}
這里調用了PatchListener的onPatchReceived方法
而PatchListener是一個接口,他的具體實現為SamplePatchListener方法,onPatchReceived在SamplePatchListener的父類DefaultPatchListener有實現,我們看下DefaultPatchListener中的onPatchReceived方法
如下
DefaultPatchListener.java
@Override
public int onPatchReceived(String path) {
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;
}
首先這個檢測了一下這個插件是否可用,通過SamplePatchListener的patchCheck方法來檢測
SamplePatchListener.java
@Override
public int patchCheck(String path) {
File patchFile = new File(path);
TinkerLog.i(TAG, "receive a patch file: %s, file size:%d", path, SharePatchFileUtil.getFileOrDirectorySize(patchFile));
int returnCode = super.patchCheck(path);
if (returnCode == ShareConstants.ERROR_PATCH_OK) {
returnCode = Utils.checkForPatchRecover(NEW_PATCH_RESTRICTION_SPACE_SIZE_MIN, maxMemory);
}
if (returnCode == ShareConstants.ERROR_PATCH_OK) {
String patchMd5 = SharePatchFileUtil.getMD5(patchFile);
SharedPreferences sp = context.getSharedPreferences(ShareConstants.TINKER_SHARE_PREFERENCE_CONFIG, Context.MODE_MULTI_PROCESS);
//optional, only disable this patch file with md5
int fastCrashCount = sp.getInt(patchMd5, 0);
if (fastCrashCount >= SampleUncaughtExceptionHandler.MAX_CRASH_COUNT) {
returnCode = Utils.ERROR_PATCH_CRASH_LIMIT;
} else {
//for upgrade patch, version must be not the same
//for repair patch, we won't has the tinker load flag
Tinker tinker = Tinker.with(context);
if (tinker.isTinkerLoaded()) {
TinkerLoadResult tinkerLoadResult = tinker.getTinkerLoadResultIfPresent();
if (tinkerLoadResult != null) {
String currentVersion = tinkerLoadResult.currentVersion;
if (patchMd5.equals(currentVersion)) {
returnCode = Utils.ERROR_PATCH_ALREADY_APPLY;
}
}
}
}
//check whether retry so many times
if (returnCode == ShareConstants.ERROR_PATCH_OK) {
returnCode = UpgradePatchRetry.getInstance(context).onPatchListenerCheck(patchMd5)
? ShareConstants.ERROR_PATCH_OK : Utils.ERROR_PATCH_RETRY_COUNT_LIMIT;
}
}
// Warning, it is just a sample case, you don't need to copy all of these
// Interception some of the request
if (returnCode == ShareConstants.ERROR_PATCH_OK) {
Properties properties = ShareTinkerInternals.fastGetPatchPackageMeta(patchFile);
if (properties == null) {
returnCode = Utils.ERROR_PATCH_CONDITION_NOT_SATISFIED;
} else {
String platform = properties.getProperty(Utils.PLATFORM);
TinkerLog.i(TAG, "get platform:" + platform);
// check patch platform require
if (platform == null || !platform.equals(BuildInfo.PLATFORM)) {
returnCode = Utils.ERROR_PATCH_CONDITION_NOT_SATISFIED;
}
}
}
SampleTinkerReport.onTryApply(returnCode == ShareConstants.ERROR_PATCH_OK);
return returnCode;
}
這里對插件是否可用進行了判斷,就不進行詳細分析了
當插件可用時候returnCode為ERROR_PATCH_OK,當不可用則會log出來失敗的errorcode
成功則調用
TinkerPatchService.runPatchService(context, path);
來啟動TinkerPatchService這個IntentService,并且把插件的路徑給傳遞到IntentService
TinkerPatchService通過onHandleIntent來接收傳遞過來的數據
TinkerPatchService.java
@Override
protected void onHandleIntent(Intent intent) {
final Context context = getApplicationContext();
Tinker tinker = Tinker.with(context);
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;
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;
tinker.getPatchReporter().
onPatchResult(patchFile, result, cost);
patchResult.isSuccess = result;
patchResult.rawPatchFilePath = path;
patchResult.costTime = cost;
patchResult.e = e;
AbstractResultService.runResultService(context, patchResult, getPatchResultExtra(intent));
}
這里首先調用了PatchReporter的onPatchServiceStart方法,而PatchReporter的實現為SamplePatchReporter
SamplePatchReporter.java
@Override
public void onPatchServiceStart(Intent intent) {
super.onPatchServiceStart(intent);
SampleTinkerReport.onApplyPatchServiceStart();
UpgradePatchRetry.getInstance(context).onPatchServiceStart(intent);
}
這里主要看UpgradePatchRetry的onPatchServiceStart方法
UpgradePatchRetry.java
public void onPatchServiceStart(Intent intent) {
if (!isRetryEnable) {
TinkerLog.w(TAG, "onPatchServiceStart retry disabled, just return");
return;
}
if (intent == null) {
TinkerLog.e(TAG, "onPatchServiceStart intent is null, just return");
return;
}
String path = TinkerPatchService.getPatchPathExtra(intent);
if (path == null) {
TinkerLog.w(TAG, "onPatchServiceStart patch path is null, just return");
return;
}
RetryInfo retryInfo;
File patchFile = new File(path);
String patchMd5 = SharePatchFileUtil.getMD5(patchFile);
if (patchMd5 == null) {
TinkerLog.w(TAG, "onPatchServiceStart patch md5 is null, just return");
return;
}
if (retryInfoFile.exists()) {
retryInfo = RetryInfo.readRetryProperty(retryInfoFile);
if (retryInfo.md5 == null || retryInfo.times == null || !patchMd5.equals(retryInfo.md5)) {
copyToTempFile(patchFile);
retryInfo.md5 = patchMd5;
retryInfo.times = "1";
} else {
int nowTimes = Integer.parseInt(retryInfo.times);
if (nowTimes >= RETRY_MAX_COUNT) {
SharePatchFileUtil.safeDeleteFile(tempPatchFile);
TinkerLog.w(TAG, "onPatchServiceStart retry more than max count, delete retry info file!");
return;
} else {
retryInfo.times = String.valueOf(nowTimes + 1);
}
}
} else {
copyToTempFile(patchFile);
retryInfo = new RetryInfo(patchMd5, "1");
}
RetryInfo.writeRetryProperty(retryInfoFile, retryInfo);
}
這里主要也做了一些驗證,并且把文件復制一份到/data/data/tinker.sample.android/tinker_temp/路徑下,然后把相關信息寫入到配置文件中
在回到TinkerPatchService的onHandleIntent方法
主要看
result = upgradePatchProcessor.tryPatch(context, path, patchResult);
這個方法的實現在UpgradePatch中
UpgradePatch.java
@Override
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 (!patchFile.isFile() || !patchFile.exists()) {
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;
}
//it is a new patch, so we should not find a exist
SharePatchInfo oldInfo = manager.getTinkerLoadResultIfPresent().patchInfo;
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;
SharePatchInfo newInfo;
//already have patch
if (oldInfo != null) {
if (oldInfo.oldVersion == null || oldInfo.newVersion == null) {
TinkerLog.e(TAG, "UpgradePatch tryPatch:onPatchInfoCorrupted");
manager.getPatchReporter().onPatchInfoCorrupted(patchFile, oldInfo.oldVersion, oldInfo.newVersion);
return false;
}
if (oldInfo.oldVersion.equals(patchMd5) || oldInfo.newVersion.equals(patchMd5)) {
TinkerLog.e(TAG, "UpgradePatch tryPatch:onPatchVersionCheckFail");
manager.getPatchReporter().onPatchVersionCheckFail(patchFile, oldInfo, patchMd5);
return false;
}
newInfo = new SharePatchInfo(oldInfo.oldVersion, patchMd5, Build.FINGERPRINT);
} else {
newInfo = new SharePatchInfo("", patchMd5, Build.FINGERPRINT);
}
//check ok, we can real recover a new patch
final String patchDirectory = manager.getPatchDirectory().getAbsolutePath();
TinkerLog.i(TAG, "UpgradePatch tryPatch:patchMd5:%s", patchMd5);
final String patchName = SharePatchFileUtil.getPatchVersionDirectory(patchMd5);
final String patchVersionDirectory = patchDirectory + "/" + patchName;
TinkerLog.i(TAG, "UpgradePatch tryPatch:patchVersionDirectory:%s", patchVersionDirectory);
//it is a new patch, we first delete if there is any files
//don't delete dir for faster retry
// SharePatchFileUtil.deleteDir(patchVersionDirectory);
//copy file
File destPatchFile = new File(patchVersionDirectory + "/" + SharePatchFileUtil.getPatchVersionFile(patchMd5));
try {
SharePatchFileUtil.copyFileUsingStream(patchFile, destPatchFile);
TinkerLog.w(TAG, "UpgradePatch after %s size:%d, %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;
}
final File patchInfoFile = manager.getPatchInfoFile();
if (!SharePatchInfo.rewritePatchInfoFileWithLock(patchInfoFile, newInfo, SharePatchFileUtil.getPatchInfoLockFile(patchDirectory))) {
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;
}
}
這里首先初始化相關數據與相關驗證,再將補丁文件拷貝到目標目錄中
SharePatchFileUtil.copyFileUsingStream(patchFile, destPatchFile);
路徑為/data/data/tinker.sample.android/tinker/patch-xxxxxx/patch-xxxxxx.apk
接下來就是調用DexDiffPatchInternal,BsDiffPatchInternal,ResDiffPatchInternal這些類的方法進行dexDiff差分的計算相關
至于相關差分的計算,由于比較復雜,我暫時還沒有深入去看,暫時埋個坑在這里,等后面找時間去填上這個坑
在回到TinkerPatchService的onHandleIntent方法
后面調用了PatchReporter的onPatchResult,這個方法主要刪除了上面拷貝在/data/data/tinker.sample.android/tinker_temp/的文件
接下來啟動了AbstractResultService,并把插件的路徑傳遞過去了
AbstractResultService的實現在SampleResultService類里面,SampleResultService的onPatchResult刪除了原始的插件文件。
到這里插件加載分析就基本結束了
寫在后面的話
<p>
插件加載分析結束了,但是卻沒有去分析dexDiff差分的計算,而這個dexDiff差分計算則是區分的Tinker與其他相同方案的熱修復庫,dexDiff是基于 Dex 的文件結構來下手,將產生變化的結構提取出來,產生的補丁非常小,而且在 diff 的過程中也處理了一些會造成補丁包很大的場景,所以等后面有時間將這一塊補上,下一篇文章則是對dex文件加載進行源碼分析了,peace~~~