上篇文章分析了加载补丁的源码,本篇文章会继续分析tinker初始化过程以及合成补丁的过程。
之前也说过,使用Tinker之前必须通过如下代码初始化Tinker:
TinkerInstaller.install(applicationLike);
这是最简单的初始化方法,也支持很多自定义参数,等我们分析完默认的情况,自定义参数也就好理解了。
先看一下这个方法的实现:
/**
* install tinker with default config, you must install tinker before you use their api
* or you can just use {@link TinkerApplicationHelper}'s api
*
* @param applicationLike
*/
public static Tinker install(ApplicationLike applicationLike) {
Tinker tinker = new Tinker.Builder(applicationLike.getApplication()).build();
Tinker.create(tinker);
tinker.install(applicationLike.getTinkerResultIntent());
return tinker;
}
Tinker自定义参数很多,所以这里使用了Builder模式初始化Tinker,这里主要看一下Builder类里面的默认实现,后面分析会用到这些默认参数:
public static class Builder {
private final Context context;
private final boolean mainProcess;
private final boolean patchProcess;
private int status = -1;
private LoadReporter loadReporter;
private PatchReporter patchReporter;
private PatchListener listener;
private File patchDirectory;
private File patchInfoFile;
private File patchInfoLockFile;
private Boolean tinkerLoadVerifyFlag;
/**
* Start building a new {@link Tinker} instance.
*/
public Builder(Context context) {
if (context == null) {
throw new TinkerRuntimeException("Context must not be null.");
}
this.context = context;
this.mainProcess = TinkerServiceInternals.isInMainProcess(context);
this.patchProcess = TinkerServiceInternals.isInTinkerPatchServiceProcess(context);
this.patchDirectory = SharePatchFileUtil.getPatchDirectory(context);
if (this.patchDirectory == null) {
TinkerLog.e(TAG, "patchDirectory is null!");
return;
}
this.patchInfoFile = SharePatchFileUtil.getPatchInfoFile(patchDirectory.getAbsolutePath());
this.patchInfoLockFile = SharePatchFileUtil.getPatchInfoLockFile(patchDirectory.getAbsolutePath());
TinkerLog.w(TAG, "tinker patch directory: %s", patchDirectory);
}
//省略了set方法
public Tinker build() {
if (status == -1) {
status = ShareConstants.TINKER_ENABLE_ALL;
}
if (loadReporter == null) {
loadReporter = new DefaultLoadReporter(context);
}
if (patchReporter == null) {
patchReporter = new DefaultPatchReporter(context);
}
if (listener == null) {
listener = new DefaultPatchListener(context);
}
if (tinkerLoadVerifyFlag == null) {
tinkerLoadVerifyFlag = false;
}
return new Tinker(context, status, loadReporter, patchReporter, listener, patchDirectory,
patchInfoFile, patchInfoLockFile, mainProcess, patchProcess, tinkerLoadVerifyFlag);
}
}
上面代码省略了set方法,我们只关注默认设置。其中mainProcess,patchProcess判断当前是否是应用进程和补丁合成进程。loadReporter,patchReporter 顾名思义是一些过程的回调。PatchListener 是我们关注的重点,也是补丁合成的入口,它的默认实现是DefaultPatchListener,下面分析会用到。
patchDirectory,patchInfoFile,patchInfoLockFile分别是:
- /data/data/package_name/tinker/
- /data/data/package_name/tinker/patch.info
- /data/data/package_name/tinker/info.lock
tinkerLoadVerifyFlag是新建Application时传进去的参数,用于判断是否每次加载都做md5校验。
初始化好Tinker之后再调用Tinker.create(tinker);
/**
* create custom tinker by {@link Tinker.Builder}
* please do it when very first your app start.
*
* @param tinker
*/
public static void create(Tinker tinker) {
if (sInstance != null) {
throw new TinkerRuntimeException("Tinker instance is already set.");
}
sInstance = tinker;
}
sInstance是静态变量,保证Tinker是单例的,并且只初始化一次。
最后调用tinker.install(applicationLike.getTinkerResultIntent());
public void install(Intent intentResult) {
install(intentResult, DefaultTinkerResultService.class, new UpgradePatch());
}
/**
* you must install tinker first!!
*
* @param intentResult
* @param serviceClass
* @param upgradePatch
*/
public void install(Intent intentResult, Class<? extends AbstractResultService> serviceClass,
AbstractPatch upgradePatch) {
sInstalled = true;
TinkerPatchService.setPatchProcessor(upgradePatch, serviceClass);
if (!isTinkerEnabled()) {
TinkerLog.e(TAG, "tinker is disabled");
return;
}
if (intentResult == null) {
throw new TinkerRuntimeException("intentResult must not be null.");
}
tinkerLoadResult = new TinkerLoadResult();
tinkerLoadResult.parseTinkerResult(getContext(), intentResult);
//after load code set
loadReporter.onLoadResult(patchDirectory, tinkerLoadResult.loadCode, tinkerLoadResult.costTime);
if (!loaded) {
TinkerLog.w(TAG, "tinker load fail!");
}
}
这里值得注意的是install方法的后面两个参数,serviceClass 是用于补丁合成成功后启动的Service来处理合成结果,upgradePatch 是真正合成补丁的类,分别提供了默认实现DefaultTinkerResultService和UpgradePatch,这两个参数也支持自定义。在install方法中会调用TinkerPatchService.setPatchProcessor(upgradePatch, serviceClass); 将这两个参数通过静态方法设置给TinkerPatchService类,TinkerPatchService类是合成补丁的Service,并且运行在新的进程中。
这样就完成了Tinker的初始化。
第一篇文章介绍过使用以下方法来加载补丁:
TinkerInstaller.onReceiveUpgradePatch(context, patchLocation)
看一下具体实现:
/**
* new patch file to install, try install them with :patch process
*
* @param context
* @param patchLocation
*/
public static void onReceiveUpgradePatch(Context context, String patchLocation) {
Tinker.with(context).getPatchListener().onPatchReceived(patchLocation);
}
这里会调用PatchListener,还记得之前这个参数的默认实现吗?
我们来看一下DefaultPatchListener的onPatchReceived方法:
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;
}
patchCheck(patch)方法会判断是否开启了Tinker,以及补丁文件是否存在。然后会启动TinkerPatchService:TinkerPatchService.runPatchService(context, path);
TinkerPatchService是继承于IntentService,IntentService与普通Service的区别这里就不说了,看它的onHandleIntent方法,继承IntentService必须实现该方法,并且可以进行耗时操作:
@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));
}
先进行了参数校验,increasingPriority(),这个方法用于提高进程优先级,防止被回收:
private void increasingPriority() {
// if (Build.VERSION.SDK_INT > 24) {
// TinkerLog.i(TAG, "for Android 7.1, we just ignore increasingPriority job");
// return;
// }
TinkerLog.i(TAG, "try to increase patch process priority");
try {
Notification notification = new Notification();
if (Build.VERSION.SDK_INT < 18) {
startForeground(notificationId, notification);
} else {
startForeground(notificationId, notification);
// start InnerService
startService(new Intent(this, InnerService.class));
}
} catch (Throwable e) {
TinkerLog.i(TAG, "try to increase patch process priority error:" + e);
}
}
然后调用result = upgradePatchProcessor.tryPatch(context, path, patchResult);进行合成补丁,返回一个结果码,这里下面再详细说,先继续往下看, 最后会启动另一个Service:
AbstractResultService.runResultService(context, patchResult, getPatchResultExtra(intent));
这个Service就是之前传进来的DefaultTinkerResultService,并且将合成结果带给它回调onPatchResult方法:
public class DefaultTinkerResultService extends AbstractResultService {
private static final String TAG = "Tinker.DefaultTinkerResultService";
/**
* we may want to use the new patch just now!!
*
* @param result
*/
@Override
public void onPatchResult(PatchResult result) {
if (result == null) {
TinkerLog.e(TAG, "DefaultTinkerResultService received null result!!!!");
return;
}
TinkerLog.i(TAG, "DefaultTinkerResultService received a result:%s ", result.toString());
//first, we want to kill the recover process
TinkerServiceInternals.killTinkerPatchServiceProcess(getApplicationContext());
// if success and newPatch, it is nice to delete the raw file, and restart at once
// only main process can load an upgrade patch!
if (result.isSuccess) {
File rawFile = new File(result.rawPatchFilePath);
if (rawFile.exists()) {
TinkerLog.i(TAG, "save delete raw patch file");
SharePatchFileUtil.safeDeleteFile(rawFile);
}
if (checkIfNeedKill(result)) {
android.os.Process.killProcess(android.os.Process.myPid());
} else {
TinkerLog.i(TAG, "I have already install the newly patch version!");
}
}
}
public boolean checkIfNeedKill(PatchResult result) {
Tinker tinker = Tinker.with(getApplicationContext());
if (tinker.isTinkerLoaded()) {
TinkerLoadResult tinkerLoadResult = tinker.getTinkerLoadResultIfPresent();
if (tinkerLoadResult != null) {
String currentVersion = tinkerLoadResult.currentVersion;
if (result.patchVersion != null && result.patchVersion.equals(currentVersion)) {
return false;
}
}
}
return true;
}
}
在onPatchResult方法中会杀死补丁合成的进程,如果补丁合成成功,会将原始数据删掉,并且杀死当前进程。当然用户也可以自定义这个类,实现更好的逻辑,比如不直接杀死当前进程,而是当用户退出应用,切到后台,或者关闭屏幕的时候杀死应用,达到重启的目的,具体实现可以参考Simple中的实现。这样整个补丁的合成过程就结束了。目前为止大致Tinker初始化以及补丁合成流程已经讲完了,有兴趣的继续往下看真正合成补丁的调用
result = upgradePatchProcessor.tryPatch(context, path, patchResult);
还记得之前初始化的方法吗:
public void install(Intent intentResult) {
install(intentResult, DefaultTinkerResultService.class, new UpgradePatch());
}
这里的UpgradePatch对象便会赋值给upgradePatchProcessor,合成补丁的时候调用它的tryPatch方法:
@Override
public boolean tryPatch(Context context, String tempPatchPath, PatchResult patchResult) {
Tinker manager = Tinker.with(context);
final File patchFile = new File(tempPatchPath);
//check the signature, we should create a new checker
ShareSecurityCheck signatureCheck = new ShareSecurityCheck(context);
int returnCode = ShareTinkerInternals.checkTinkerPackage(context, manager.getTinkerFlags(), patchFile, signatureCheck);
//it is a new patch, so we should not find a exist
SharePatchInfo oldInfo = manager.getTinkerLoadResultIfPresent().patchInfo;
String patchMd5 = SharePatchFileUtil.getMD5(patchFile);
//use md5 as version
patchResult.patchVersion = patchMd5;
SharePatchInfo newInfo;
//already have patch
if (oldInfo != null) {
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();
final String patchName = SharePatchFileUtil.getPatchVersionDirectory(patchMd5);
final String patchVersionDirectory = patchDirectory + "/" + patchName;
//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;
}
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;
}
这个方法比较长,删除了一些校验的代码以及合成资源文件等方法,主要看dex文件的合成过程。开始是初始化一些目录,再将补丁文件拷贝到目标目录中:
SharePatchFileUtil.copyFileUsingStream(patchFile, destPatchFile);
再调用以下方法:
DexDiffPatchInternal.tryRecoverDexFiles(manager, signatureCheck, context, patchVersionDirectory, destPatchFile)
看具体实现:
protected static boolean tryRecoverDexFiles(Tinker manager, ShareSecurityCheck checker, Context context,
String patchVersionDirectory, File patchFile) {
if (!manager.isEnabledForDex()) {
TinkerLog.w(TAG, "patch recover, dex is not enabled");
return true;
}
String dexMeta = checker.getMetaContentMap().get(DEX_META_FILE);
if (dexMeta == null) {
TinkerLog.w(TAG, "patch recover, dex is not contained");
return true;
}
long begin = SystemClock.elapsedRealtime();
boolean result = patchDexExtractViaDexDiff(context, patchVersionDirectory, dexMeta, patchFile);
long cost = SystemClock.elapsedRealtime() - begin;
TinkerLog.i(TAG, "recover dex result:%b, cost:%d", result, cost);
return result;
}
主要就是计算耗时,最终方法是patchDexExtractViaDexDiff:
private static boolean patchDexExtractViaDexDiff(Context context, String patchVersionDirectory, String meta, final File patchFile) {
String dir = patchVersionDirectory + "/" + DEX_PATH + "/";
if (!extractDexDiffInternals(context, dir, meta, patchFile, TYPE_DEX)) {
TinkerLog.w(TAG, "patch recover, extractDiffInternals fail");
return false;
}
final Tinker manager = Tinker.with(context);
File dexFiles = new File(dir);
File[] files = dexFiles.listFiles();
if (files != null) {
final String optimizeDexDirectory = patchVersionDirectory + "/" + DEX_OPTIMIZE_PATH + "/";
File optimizeDexDirectoryFile = new File(optimizeDexDirectory);
if (!optimizeDexDirectoryFile.exists() && !optimizeDexDirectoryFile.mkdirs()) {
TinkerLog.w(TAG, "patch recover, make optimizeDexDirectoryFile fail");
return false;
}
TinkerLog.w(TAG, "patch recover, try to optimize dex file count:%d", files.length);
boolean isSuccess = TinkerParallelDexOptimizer.optimizeAll(
files, optimizeDexDirectoryFile,
new TinkerParallelDexOptimizer.ResultCallback() {
long startTime;
@Override
public void onStart(File dexFile, File optimizedDir) {
startTime = System.currentTimeMillis();
TinkerLog.i(TAG, "start to optimize dex %s", dexFile.getPath());
}
@Override
public void onSuccess(File dexFile, File optimizedDir) {
// Do nothing.
TinkerLog.i(TAG, "success to optimize dex %s use time %d",
dexFile.getPath(), (System.currentTimeMillis() - startTime));
}
@Override
public void onFailed(File dexFile, File optimizedDir, Throwable thr) {
TinkerLog.i(TAG, "fail to optimize dex %s use time %d",
dexFile.getPath(), (System.currentTimeMillis() - startTime));
SharePatchFileUtil.safeDeleteFile(dexFile);
manager.getPatchReporter().onPatchDexOptFail(patchFile, dexFile, optimizeDexDirectory, dexFile.getName(), thr);
}
}
);
//list again
if (isSuccess) {
for (File file : files) {
try {
if (!SharePatchFileUtil.isLegalFile(file)) {
TinkerLog.e(TAG, "single dex optimizer file %s is not exist, just return false", file);
return false;
}
String outputPathName = SharePatchFileUtil.optimizedPathFor(file, optimizeDexDirectoryFile);
File outputFile = new File(outputPathName);
if (!SharePatchFileUtil.isLegalFile(outputFile)) {
TinkerLog.e(TAG, "parallel dex optimizer file %s fail, optimize again", outputPathName);
long start = System.currentTimeMillis();
DexFile.loadDex(file.getAbsolutePath(), outputPathName, 0);
TinkerLog.i(TAG, "success single dex optimize file, path: %s, use time: %d", file.getPath(), (System.currentTimeMillis() - start));
if (!SharePatchFileUtil.isLegalFile(outputFile)) {
manager.getPatchReporter()
.onPatchDexOptFail(patchFile, file, optimizeDexDirectory,
file.getName(), new TinkerRuntimeException("dexOpt file:" + outputPathName + " is not exist"));
return false;
}
}
} catch (Throwable e) {
TinkerLog.e(TAG, "dex optimize or load failed, path:" + file.getPath());
//delete file
SharePatchFileUtil.safeDeleteFile(file);
manager.getPatchReporter().onPatchDexOptFail(patchFile, file, optimizeDexDirectory, file.getName(), e);
return false;
}
}
}
return isSuccess;
}
return true;
}
首先extractDexDiffInternals(context, dir, meta, patchFile, TYPE_DEX)是合成全量补丁,后面是通过DexFile.loadDex生成优化后的dex文件,这个过程貌似做了两遍。主要看extractDexDiffInternals,哎,不贴代码了,代码好多,自己看吧。这个方法中会拿到两个文件,一个原始包文件,一个是补丁文件:
apk = new ZipFile(apkPath);
patch = new ZipFile(patchFile);
安全性校验完了之后会分别调用extractDexFile(zipFile, entryFile,extractTo, dexInfo) 或者 patchDexFile(baseApk, patchPkg, oldDexEntry, patchFileEntry, patchInfo, patchedDexFile);这里分了三种情况,
第一种情况是直接将补丁包中的dex文件拷贝到了目标文件夹下,这种情况应该是下发的补丁包就是全量包;
第二种情况是直接拷贝原apk包的dex文件,有这么一段注释:
// Small patched dex generating strategy was disabled, we copy full original dex directly now.
为什么要把原始Apk包里的dex文件复制过去呢?我也想不明白,问了一下张绍文老大,他的回答是:
因为内联以及地址错乱的问题
对,就是这个原因(因回答过于简洁,还是不太明白)。有知道的小伙伴欢迎留言告知,说的稍微详细一点。
这两种情况调用的是extractDexFile,不同的是传进去的包不一样,一个是补丁包,一个是原始包。
第三种情况是将原始dex与补丁dex合成全量dex,调用patchDexFile,最终调用如下方法合成补丁:
new DexPatchApplier(zis, (int) entry.getSize(), patchFileStream).executeAndSaveTo(zos);
继续往下看DexPatchApplier类,这个可是合成dex文件的核心所在
额。。。不看了,看不下去了,有点想吐,晕代码。。。
等不晕的时候再来看吧,分析Tinker源码的文章就暂时告一段落了,对这系列文章有疑问的,或者发现写的有错误的欢迎在下方留言;如果接入遇到问题的也可以留言。