先要配置這麼一段話:後面會談到爲什麼以及如何改變這些默認設置
public static void installTinker(ApplicationLike appLike) {
//or you can just use DefaultLoadReporter
LoadReporter loadReporter = new DefaultLoadReporter(appLike.getApplication());
//or you can just use DefaultPatchReporter
PatchReporter patchReporter = new DefaultPatchReporter(appLike.getApplication());
//or you can just use DefaultPatchListener
PatchListener patchListener = new DefaultPatchListener(appLike.getApplication());
//you can set your own upgrade patch if you need
AbstractPatch upgradePatchProcessor = new UpgradePatch();
TinkerInstaller.install(appLike,
loadReporter, patchReporter, patchListener,
TinkerResultService.class, upgradePatchProcessor);
isInstalled = true;
}
通過下面這句話啓動熱修復
TinkerInstaller.onReceiveUpgradePatch(mContext, saveFileName);
看一下接下來的函數調用:
public static void onReceiveUpgradePatch(Context context, String patchLocation) {
Tinker.with(context).getPatchListener().onPatchReceived(patchLocation);
}
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;
}
public static void runPatchService(Context context, String path) {
try {
Intent intent = new Intent(context, TinkerPatchService.class);//注意這裏,是一個IntentService,所以調用onHandleIntent
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);
}
}
這裏的onHandleIntent是很重要的。所以拿出來解釋
//代碼有縮減。重要的函數已經通過*標註
protected void onHandleIntent(Intent intent) {
final Context context = getApplicationContext();
Tinker tinker = Tinker.with(context);
tinker.getPatchReporter().onPatchServiceStart(intent);//*************************************
String path = getPatchPathExtra(intent);//獲取存放在intent裏面的PATCH_PATH_EXTRA的值,說白了就是補丁的路徑
File patchFile = new File(path);
long begin = SystemClock.elapsedRealtime();
boolean result;
long cost;
Throwable e = null;
PatchResult patchResult = new PatchResult();
try {
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));//*************************************
}
看到tinker.getPatchReporter()了麼?就是我們一開始配置的DefaultPatchReporter。但是會發現onPatchServiceStart,onPatchResult就打了點Log。如果覺得還不夠,我們可以實現自定義接口—只要繼承DefaultPatchReporter並且實現他的幾個接口(比如彈出提示語增加界面友好度)
好了,接下來看tryPatch和runResultService函數
(1)tryPatch
upgradePatchProcessor其實就是AbstractPatch。最終在UpgradePatch中實現
public boolean tryPatch(Context context, String tempPatchPath, PatchResult patchResult) {
Tinker manager = Tinker.with(context);
final File patchFile = new File(tempPatchPath);
//檢查簽名
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 (!SharePatchFileUtil.checkIfMd5Valid(patchMd5)) {
TinkerLog.e(TAG, "UpgradePatch tryPatch:onPatchVersionCheckFail md5 %s is valid", patchMd5);
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 {
// 檢查MD5
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) {
......
}
//----------------這裏真正開始修復補丁
//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;
}
......
return true;
}
所以熱修復的核心如下:拿到補丁apk,檢查簽名,MD5。然後對dex,library,resource文件分別進行解壓比較覆蓋更新。(至於如何解壓覆蓋安裝的,我只看到一個有zip字眼的類,具體怎麼處理的我也搞不清了)
(2)runResultService
AbstractResultService.runResultService(context, patchResult, getPatchResultExtra(intent));
這句話中的getPatchResultExtra返回的是RESULT_CLASS_EXTRA對應的字符串
public static void runResultService(Context context, PatchResult result, String resultServiceClass) {
try {
Intent intent = new Intent();
intent.setClassName(context, resultServiceClass);
intent.putExtra(RESULT_EXTRA, result);
context.startService(intent);
} catch (Throwable throwable) {
TinkerLog.e(TAG, "run result service fail, exception:" + throwable);
}
}
看一下resultServiceClass是啥?
Class
@Override
protected void onHandleIntent(Intent intent) {
if (intent == null) {
TinkerLog.e(TAG, "AbstractResultService received a null intent, ignoring.");
return;
}
PatchResult result = (PatchResult) ShareIntentUtil.getSerializableExtra(intent, RESULT_EXTRA);
onPatchResult(result);
}
public abstract void onPatchResult(PatchResult result);
說白了就是可以寫個Service繼承AbstractResultService來實現onPatchResult。庫裏面有個默認的DefaultTinkerResultService,但是隻有刪除文件。我們可以自己寫一個Service繼承DefaultTinkerResultService來讓整個程序可以繼續進行(一般加載完補丁會有些界面上的調整)