您的位置:首页 > 其它

源码阅读--腾讯Tinker热修复框架

2017-07-19 16:44 357 查看
先要配置这么一段话:后面会谈到为什么以及如何改变这些默认设置

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来让整个程序可以继续进行(一般加载完补丁会有些界面上的调整)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: