声明:本文已授权微信公众号 YYGeeker 独家发布。博主原创文章,转载请注明出处:小嵩的博客
本系列传送门:
微信Tinker 热修复介绍及接入(一)
Tinker 原理深入理解(二)
Tinker 合并及加载补丁过程源码分析 (三)
前言
上篇文章我们讲了Tinker实现的主要原理,本篇文章主要对Tinker源码中补丁安装合并以及加载过程进行分析,本文分析基于Tinker 1.9.8 版本。主要内容有以下几点:
- 安装合并补丁包过程。
- 加载补丁过程分析。
- 加载补丁资源过程分析。
- 加载补丁SO文件分析。
一、安装合并补丁包过程分析
时序图如下:
大致流程:
1.1 在代理 Application 中初始化 Tinker 相关。
我们可以看到Tinker方案中,TinkerApplication继承自Application,也就是说它才是应用真正的Application。Tinker方法使用了ApplicationLike 来代理我们的Application。因此在代理类ApplicationLike 的实现类(demo中是SampleApplicationLike)中对Tinker进行了一些初始化操作。我们可以来看看代码:
TinkerManager中创建了几种Reporter 以及 UpgradePatch 对象。
1.2 调用入口(App主进程)。
当补丁包下发到本地,调用它开始补丁合成:
TinkerInstaller.onReceiveUpgradePatch(getApplicationContext(), Environment.getExternalStorageDirectory().getAbsolutePath() + "/patch_signed_7zip.apk");
1.3 获取监听器,并调用TinkerPatchService。
TinkerInstaller类中调用Tinker对象实例,然后获取Listener并调用onPatchReceived方法:
public class TinkerInstaller {
//省略部分代码...
public static void onReceiveUpgradePatch(Context context, String patchLocation) {
Tinker.with(context).getPatchListener().onPatchReceived(patchLocation);
}
...
}
public class DefaultPatchListener implements PatchListener {
//省略部分代码...
@Override
public int onPatchReceived(String path) {
File patchFile = new File(path);
int returnCode = patchCheck(path, SharePatchFileUtil.getMD5(patchFile));//校验Patch 合法性。
if (returnCode == ShareConstants.ERROR_PATCH_OK) {
TinkerPatchService.runPatchService(context, path);//开启Serive进程服务进行合并Patch
} else {
Tinker.with(context).getLoadReporter().onLoadPatchListenerReceiveFail(new File(path), returnCode);
}
return returnCode;
}
//省略部分代码...
}
1.4 开启Service服务或者JobScheduler合并Patch。
public class TinkerPatchService {
//省略部分代码...
public static void runPatchService(final Context context, final String path) {
try {
if (Build.VERSION.SDK_INT < MIN_SDKVER_TO_USE_JOBSCHEDULER) {
runPatchServiceByIntentService(context, path);
} else {
try {
runPatchServiceByJobScheduler(context, path);
} catch (Throwable ignored) {
// ignored.
}
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
TinkerLog.i(TAG, "check if patch service is running.");
if (!TinkerServiceInternals.isTinkerPatchServiceRunning(context)) {
TinkerLog.w(TAG, "patch service is not running, retry with IntentService.");
try {
runPatchServiceByIntentService(context, path);
TinkerLog.i(TAG, "successfully start patch service with IntentService.");
} catch (Throwable thr) {
TinkerLog.e(TAG, "failure to start patch service with IntentService. osver: %s, manu: %s, msg: %s", Build.VERSION.SDK_INT, Build.MANUFACTURER, thr.toString());
}
}
}
}, TimeUnit.SECONDS.toMillis(5));
}
} catch (Throwable throwable) {
TinkerLog.e(TAG, "start patch service fail, exception:" + throwable);
}
}
}
我们通过源码可以看到,TinkerPatchService 是合并补丁时,比较重要的一个类。runPatchService 方法中,对于Android8.0以下版本使用了 IntentService,并提高服务优先级来避免Patch进程被系统kill掉。针对 Android 8.0 版本额外做了兼容,通过 JobScheduler 以及 Hanlder 循环来保证能够正常开启多进程服务进行补丁合并。
1.5 调用doApplyPatch方法执行合并及校验操作。
public class TinkerPatchService {
//省略部分代码...
private static void doApplyPatch(Context context, Intent intent) {
//前面这段代码做了一些校验和琐碎逻辑。
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);
}
//省略部分代码,主要做了一些监听器的回调。
}
}
可以看到,doApplyPatch 方法中调用了我们在1.1节所述,即 Tinker 初始化时所 new 出来的 UpgradePatch 对象。
1.6 调用UpgradePatch执行合并补丁逻辑。
public class UpgradePatch extends AbstractPatch {
@Override
public boolean tryPatch(Context context, String tempPatchPath, PatchResult patchResult) {
//省略部分代码,主要做了一些校验逻辑及文件拷贝删除。
//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;
}
可以看到,我们的补丁包真正合并的逻辑都是在这里执行的,通过DexDiff、BsDiff、ResDiff 类分别去合并补丁包的Dex类文件,So文件,Resource资源。
1.7 补丁合并完成,回调告知主进程补丁合并结果。
public class TinkerPatchService {
//省略部分代码...
private static void