前言
对于重要类的分析已经全部加上了详细注释,代码在github.com/ColorfulHor…
上一篇文章我们分析了补丁包的生成,本文从源码开始简单分析补丁包的合成过程,限于篇幅重点分析Dex合成过程,涉及到的知识包括dex加载机制,Art编译机制等。
Tinker一个基线包可以打多个补丁,因为每一个补丁包都是在基线包上patch出来的,合成后的产物会保留在单独的目录,互不影响,也不影响原始app,可以随时清除回到基线版本。
初始化Tinker
想要使用Tinker提供的API需要先调用tinker.install进行初始化,否则只能使用TinkerApplicationHelper中的api,初始化tinker支持一些自定义配置,主要是一些回调注册,比如加载补丁回调,合成补丁回调等,可选参数如下
- applicationLike application代理
- loadReporter 加载补丁的回调类,默认DefaultLoadReporter
- patchReporter 合成补丁的回调类,默认DefaultPatchReporter
- listener 接收合成补丁任务的类,默认DefaultPatchListener
- resultServiceClass patch补丁合成进程将合成结果返回给主进程的类,默认DefaultTinkerResultService
- upgradePatchProcessor 执行补丁合成操作的类,默认UpgradePatch
public static Tinker install(ApplicationLike applicationLike, LoadReporter loadReporter, PatchReporter patchReporter,
PatchListener listener, Class<? extends AbstractResultService> resultServiceClass,
AbstractPatch upgradePatchProcessor) {
// 创建实例,注册一些回调
Tinker tinker = new Tinker.Builder(applicationLike.getApplication())
.tinkerFlags(applicationLike.getTinkerFlags())
.loadReport(loadReporter)
.listener(listener)
.patchReporter(patchReporter)
// 加载补丁是否校验md5
.tinkerLoadVerifyFlag(applicationLike.getTinkerLoadVerifyFlag()).build();
Tinker.create(tinker);
// getTinkerResultIntent得到的是此次启动加载补丁的结果信息
tinker.install(applicationLike.getTinkerResultIntent(), resultServiceClass, upgradePatchProcessor);
return tinker;
}
public void install(Intent intentResult, Class<? extends AbstractResultService> serviceClass,
AbstractPatch upgradePatch) {
sInstalled = true;
// patch service设置用于补丁合成的UpgradePatch实例,以及合成结果回调类DefaultTinkerResultService
TinkerPatchService.setPatchProcessor(upgradePatch, serviceClass);
......
tinkerLoadResult = new TinkerLoadResult();
tinkerLoadResult.parseTinkerResult(getContext(), intentResult);
// 回调补丁加载结果
loadReporter.onLoadResult(patchDirectory, tinkerLoadResult.loadCode, tinkerLoadResult.costTime);
}
补丁合成
下载补丁成功后通过调用TinkerInstaller.onReceiveUpgradePatch
合成补丁,然后直接调用到DefaultPatchListener.onPatchReceived
方法,此方法中首先调用patchCheck
检查补丁是否应该被合成,然后启动TinkerPatchService
合成补丁。
public class DefaultPatchListener implements PatchListener {
public int onPatchReceived(String path) {
final File patchFile = new File(path);
// 差异apk md5
final String patchMD5 = SharePatchFileUtil.getMD5(patchFile);
// 校验补丁
// 检查是否应该合成该补丁
// 补丁不合法/正在合成补丁/系统OTA后第一次启动
//该版本补丁已经被加载/该版本补丁已经合成还未加载/该补丁合并失败超过阈值
final int returnCode = patchCheck(path, patchMD5);
if (returnCode == ShareConstants.ERROR_PATCH_OK) {
// 绑定TinkerPatchForeService,它运行在:patch进程
runForgService();
// 启动TinkerPatchService合成补丁
TinkerPatchService.runPatchService(context, path);
} else {
Tinker.with(context).getLoadReporter().onLoadPatchListenerReceiveFail(new File(path), returnCode);
}
return returnCode;
}
}
是否需要合成补丁
patchCheck
主要检查补丁包合法性以及该版本补丁是否已经加载/合成过,以此判断是否需要合成该补丁,要注意的是在系统进行OTA升级后第一次启动也不能合成补丁,因为此时是以解释模式运行,需要重新进行dexopt。这里贴一下校验过程注释代码。
关于interpretMode(解释模式)的相关逻辑用于加载补丁,会单独在加载补丁中分析,这里不用太关注。
protected int patchCheck(String path, String patchMd5) {
final Tinker manager = Tinker.with(context);
// 是否启用了tinker
if (!manager.isTinkerEnabled() || !ShareTinkerInternals.isTinkerEnableWithSharedPreferences(context)) {
return ShareConstants.ERROR_PATCH_DISABLE;
}
// md5以及文件合法性校验
if (TextUtils.isEmpty(patchMd5)) {
return ShareConstants.ERROR_PATCH_NOTEXIST;
}
final File file = new File(path);
if (!SharePatchFileUtil.isLegalFile(file)) {
return ShareConstants.ERROR_PATCH_NOTEXIST;
}
// 不能在patch进程调用
if (manager.isPatchProcess()) {
return ShareConstants.ERROR_PATCH_INSERVICE;
}
// patch进程已经在运行则忽略
if (TinkerServiceInternals.isTinkerPatchServiceRunning(context)) {
return ShareConstants.ERROR_PATCH_RUNNING;
}
// 这里判断是否在7.0以下系统错误地开启了jit选项(7.0以上Art才重新引入jit,某些自定义rom会错误打开此选项)
if (ShareTinkerInternals.isVmJit()) {
return ShareConstants.ERROR_PATCH_JIT;
}
// 此次启动加载补丁的信息
final TinkerLoadResult loadResult = manager.getTinkerLoadResultIfPresent();
// 当前是否在以解释模式运行,true则说明需要重新做dexopt
final boolean repairOptNeeded = manager.isMainProcess()
&& loadResult != null && loadResult.useInterpretMode;
if (!repairOptNeeded) {
if (manager.isTinkerLoaded() && loadResult != null) {
String currentVersion = loadResult.currentVersion;
// 该版本的补丁已经被加载则忽略
if (patchMd5.equals(currentVersion)) {
return ShareConstants.ERROR_PATCH_ALREADY_APPLY;
}
}
// 该补丁已经合成,但是主进程没有重启加载,这里也忽略,不再重复合成
final String patchDirectory = manager.getPatchDirectory().getAbsolutePath();
File patchInfoLockFile = SharePatchFileUtil.getPatchInfoLockFile(patchDirectory);
File patchInfoFile = SharePatchFileUtil.getPatchInfoFile(patchDirectory);
try {
final SharePatchInfo currInfo = SharePatchInfo.readAndCheckPropertyWithLock(patchInfoFile, patchInfoLockFile);
if (currInfo != null && !ShareTinkerInternals.isNullOrNil(currInfo.newVersion) && !currInfo.isRemoveNewVersion) {
if (patchMd5.equals(currInfo.newVersion)) {
return ShareConstants.ERROR_PATCH_ALREADY_APPLY;
}
}
} catch (Throwable ignored) {
// Ignored.
}
}
// 合成补丁重试次数超过阈值(20次)忽略
if (!UpgradePatchRetry.getInstance(context).onPatchListenerCheck(patchMd5)) {
return ShareConstants.ERROR_PATCH_RETRY_COUNT_LIMIT;
}
return ShareConstants.ERROR_PATCH_OK;
}
TinkerPatchService合成补丁预操作
TinkerPatchService是一个IntentService,运行在:patch合成进程,主要做一下补丁合成的预操作,主要逻辑在doApplyPatch
方法中。
- 调用
DefaultPatchReporter.onPatchServiceStart
回调,记录合成该补丁重试次数 - 调用
UpgradePatch.tryPatch
真正开始合成补丁 - 合成完毕回调结果到
DefaultTinkerResultService
,删除下载的补丁源文件,杀死进程重启
public class TinkerPatchService extends IntentService {
......
public static void runPatchService(final Context context, final String path) {
ShareTinkerLog.i(TAG, "run patch service...");
Intent intent = new Intent(context, TinkerPatchService.class);
// 补丁路径
intent.putExtra(PATCH_PATH_EXTRA, path);
// 合成结果回调类名,默认DefaultTinkerResultService
intent.putExtra(RESULT_CLASS_EXTRA, resultServiceClass.getName());
try {
context.startService(intent);
} catch (Throwable thr) {
ShareTinkerLog.e(TAG, "run patch service fail, exception:" + thr);
}
}
@Override
protected void onHandleIntent(Intent intent) {
// 设为前台服务提高优先级
increasingPriority();
doApplyPatch(this, intent);
}
private static AtomicBoolean sIsPatchApplying = new AtomicBoolean(false);
private static void doApplyPatch(Context context, Intent intent) {
// Since we may retry with IntentService, we should prevent
// racing here again.
if (!sIsPatchApplying.compareAndSet(false, true)) {
ShareTinkerLog.w(TAG, "TinkerPatchService doApplyPatch is running by another runner.");
return;
}
Tinker tinker = Tinker.with(context);
// 回调事件
tinker.getPatchReporter().onPatchServiceStart(intent);
if (intent == null) {
ShareTinkerLog.e(TAG, "TinkerPatchService received a null intent, ignoring.");
return;
}
// 补丁文件路径
String path = getPatchPathExtra(intent);
if (path == null) {
ShareTinkerLog.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;
PatchResult patchResult = new PatchResult();
try {
if (upgradePatchProcessor == null) {
throw new TinkerRuntimeException("upgradePatchProcessor is null.");
}
// 调用UpgradePatch tryPatch合成补丁
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;
// 回调合成结果到DefaultTinkerResultService
AbstractResultService.runResultService(context, patchResult, getPatchResultExtra(intent));
sIsPatchApplying.set(false);
}
}
开始合成补丁
默认UpgradePatch.tryPatch
执行补丁合成逻辑,大致步骤如下
ShareSecurityCheck
类提取补丁包中xxx_meta文件,并校验签名ShareTinkerInternals
解析并校验补丁的TinkerId、签名、MD5、meta文件等的合法性- 读取解析data/data/包名/tinker/patch.info文件,校验该补丁是否可以被合成;此文件记录当前版本加载补丁的信息,对应类为
SharePatchInfo
- 将补丁包改名为patch-xxx.apk,拷贝到data/data/包名/tinker/patch-xxx目录
- 分别合成补丁中dex文件,资源文件,so库,合成dex后对新dex做dex2oat操作 本文主要对Dex合成流程进行分析,so库以及资源文件合成部分比较简单,只需要关心输出目录即可
public class UpgradePatch extends AbstractPatch {
private static final String TAG = "Tinker.UpgradePatch";
@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)) {
return false;
}
if (!SharePatchFileUtil.isLegalFile(patchFile)) {
return false;
}
// 此类用于读取补丁包中的meta文件
ShareSecurityCheck signatureCheck = new ShareSecurityCheck(context);
// 解析并校验补丁的TinkerId、签名、MD5、meta文件等的合法性
int returnCode = ShareTinkerInternals.checkTinkerPackage(context, manager.getTinkerFlags(), patchFile, signatureCheck);
if (returnCode != ShareConstants.ERROR_PACKAGE_CHECK_OK) {
manager.getPatchReporter().onPatchPackageCheckFail(patchFile, returnCode);
return false;
}
String patchMd5 = SharePatchFileUtil.getMD5(patchFile);
if (patchMd5 == null) {
return false;
}
// 补丁包md5作为版本号
patchResult.patchVersion = patchMd5;
ShareTinkerLog.i(TAG, "UpgradePatch tryPatch:patchMd5:%s", patchMd5);
// data/data/包名/tinker
final String patchDirectory = manager.getPatchDirectory().getAbsolutePath();
File patchInfoLockFile = SharePatchFileUtil.getPatchInfoLockFile(patchDirectory);
// data/data/包名/tinker/patch.info
File patchInfoFile = SharePatchFileUtil.getPatchInfoFile(patchDirectory);
// 读取package_meta.txt内容
final Map<String, String> pkgProps = signatureCheck.getPackagePropertiesIfPresent();
if (pkgProps == null) {
ShareTinkerLog.e(TAG, "UpgradePatch packageProperties is null, do we process a valid patch apk ?");
return false;
}
final String isProtectedAppStr = pkgProps.get(ShareConstants.PKGMETA_KEY_IS_PROTECTED_APP);
final boolean isProtectedApp = (isProtectedAppStr != null && !isProtectedAppStr.isEmpty() && !"0".equals(isProtectedAppStr));
// 读取上一次合成补丁的信息
SharePatchInfo oldInfo = SharePatchInfo.readAndCheckPropertyWithLock(patchInfoFile, patchInfoLockFile);
SharePatchInfo newInfo;
if (oldInfo != null) {
// 已经加载过补丁
if (oldInfo.oldVersion == null || oldInfo.newVersion == null || oldInfo.oatDir == null) {
ShareTinkerLog.e(TAG, "UpgradePatch tryPatch:onPatchInfoCorrupted");
manager.getPatchReporter().onPatchInfoCorrupted(patchFile, oldInfo.oldVersion, oldInfo.newVersion);
return false;
}
if (!SharePatchFileUtil.checkIfMd5Valid(patchMd5)) {
ShareTinkerLog.e(TAG, "UpgradePatch tryPatch:onPatchVersionCheckFail md5 %s is valid", patchMd5);
manager.getPatchReporter().onPatchVersionCheckFail(patchFile, oldInfo, patchMd5);
return false;
}
// 当前是否以解释模式加载了补丁(系统OTA之后第一次运行)
// 在TinkerLoader中以解释模式加载了补丁,oatDir会被设为interpret
final boolean usingInterpret = oldInfo.oatDir.equals(ShareConstants.INTERPRET_DEX_OPTIMIZE_PATH);
// 判断该补丁是否已合成过,当前要合成的补丁已经被加载则不合成
if (!usingInterpret && !ShareTinkerInternals.isNullOrNil(oldInfo.newVersion) && oldInfo.newVersion.equals(patchMd5) && !oldInfo.isRemoveNewVersion) {
ShareTinkerLog.e(TAG, "patch already applied, md5: %s", patchMd5);
// 该补丁已经合成成功过,合成重试次数重置为1
UpgradePatchRetry.getInstance(context).onPatchResetMaxCheck(patchMd5);
return true;
}
// 当前是以解释模式加载补丁时,将oatDir设为changing,以便下次加载补丁时不再以解释模式加载
final String finalOatDir = usingInterpret ? ShareConstants.CHANING_DEX_OPTIMIZE_PATH : oldInfo.oatDir;
newInfo = new SharePatchInfo(oldInfo.oldVersion, patchMd5, isProtectedApp, false, Build.FINGERPRINT, finalOatDir, false);
} else {
// 没有加载过补丁
newInfo = new SharePatchInfo("", patchMd5, isProtectedApp, false, Build.FINGERPRINT, ShareConstants.DEFAULT_DEX_OPTIMIZE_PATH, false);
}
final String patchName = SharePatchFileUtil.getPatchVersionDirectory(patchMd5);
// data/data/包名/tinker/patch-xxx
final String patchVersionDirectory = patchDirectory + "/" + patchName;
ShareTinkerLog.i(TAG, "UpgradePatch tryPatch:patchVersionDirectory:%s", patchVersionDirectory);
// data/data/包名/tinker/patch-xxx/patch-xxx.apk
File destPatchFile = new File(patchVersionDirectory + "/" + SharePatchFileUtil.getPatchVersionFile(patchMd5));
try {
if (!patchMd5.equals(SharePatchFileUtil.getMD5(destPatchFile))) {
// 将补丁包改名拷贝到destPatchFile目录
SharePatchFileUtil.copyFileUsingStream(patchFile, destPatchFile);
ShareTinkerLog.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) {
ShareTinkerLog.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;
}
// 合成dex文件,并且进行dex2oat
if (!DexDiffPatchInternal.tryRecoverDexFiles(manager, signatureCheck, context, patchVersionDirectory, destPatchFile, patchResult)) {
ShareTinkerLog.e(TAG, "UpgradePatch tryPatch:new patch recover, try patch dex failed");
return false;
}
// 方舟编译器相关处理
if (!ArkHotDiffPatchInternal.tryRecoverArkHotLibrary(manager, signatureCheck,
context, patchVersionDirectory, destPatchFile)) {
return false;
}
// BSDiff合并so库,验证流程和合并dex差不多,最后通过BSPatch.patchFast合成
if (!BsDiffPatchInternal.tryRecoverLibraryFiles(manager, signatureCheck, context, patchVersionDirectory, destPatchFile)) {
ShareTinkerLog.e(TAG, "UpgradePatch tryPatch:new patch recover, try patch library failed");
return false;
}
// BSDiff合并资源文件,验证流程和合并dex差不多,最后通过BSPatch.patchFast合成
// 输出目录data/data/包名/tinker/patch-xxx/res/resources.apk
if (!ResDiffPatchInternal.tryRecoverResourceFiles(manager, signatureCheck, context, patchVersionDirectory, destPatchFile)) {
ShareTinkerLog.e(TAG, "UpgradePatch tryPatch:new patch recover, try patch resource failed");
return false;
}
// 检查dex2oat产物是否有缺失,因为vivo/oppo会异步执行dex2oat
if (!DexDiffPatchInternal.waitAndCheckDexOptFile(patchFile, manager)) {
ShareTinkerLog.e(TAG, "UpgradePatch tryPatch:new patch recover, check dex opt file failed");
return false;
}
// 将合成补丁信息写入到patch.info文件
if (!SharePatchInfo.rewritePatchInfoFileWithLock(patchInfoFile, newInfo, patchInfoLockFile)) {
ShareTinkerLog.e(TAG, "UpgradePatch tryPatch:new patch recover, rewrite patch info failed");
manager.getPatchReporter().onPatchInfoCorrupted(patchFile, newInfo.oldVersion, newInfo.newVersion);
return false;
}
// 重置记录的补丁合成次数
UpgradePatchRetry.getInstance(context).onPatchResetMaxCheck(patchMd5);
return true;
}
}
dex预合成
补丁合成最关键的是dex文件合成,我们着重分析这部分逻辑,这里还是列一下简单流程
- 入口是
DexDiffPatchInternal.tryRecoverDexFiles
方法,然后调用到patchDexExtractViaDexDiff
patchDexExtractViaDexDiff
方法中extractDexDiffInternals
方法对dex做一些预校验,然后开始合成- 最后调用
dexOptimizeDexFiles
对合成后dex做dexopt操作,产物保存在data/data/包名/tinker/patch-xxx/odex
public class DexDiffPatchInternal extends BasePatchInternal {
private static boolean patchDexExtractViaDexDiff(Context context, String patchVersionDirectory, String meta, final File patchFile, PatchResult patchResult) {
// data/data/包名/tinker/patch-xxx/dex
String dir = patchVersionDirectory + "/" + DEX_PATH + "/";
// 合成dex
if (!extractDexDiffInternals(context, dir, meta, patchFile, TYPE_DEX)) {
return false;
}
File dexFiles = new File(dir);
File[] files = dexFiles.listFiles();
// 存放合成结果文件
List<File> legalFiles = new ArrayList<>();
if (files != null) {
for (File file : files) {
final String fileName = file.getName();
// may have directory in android o
if (file.isFile()
&& (fileName.endsWith(ShareConstants.DEX_SUFFIX)
|| fileName.endsWith(ShareConstants.JAR_SUFFIX)
|| fileName.endsWith(ShareConstants.PATCH_SUFFIX))
) {
legalFiles.add(file);
}
}
}
ShareTinkerLog.i(TAG, "legal files to do dexopt: " + legalFiles);
// 存放dexopt产物,data/data/包名/tinker/patch-xxx/odex
final String optimizeDexDirectory = patchVersionDirectory + "/" + DEX_OPTIMIZE_PATH + "/";
// 触发执行dexopt
return dexOptimizeDexFiles(context, legalFiles, optimizeDexDirectory, patchFile, patchResult);
}
}
dex合成
DexDiffPatchInternal.extractDexDiffInternals
方法对于dex做预校验,具体实现在DexPatchApplier
中,大致流程如下
- 解析补丁包中dex_meta文件,将变更dex信息解析为ShareDexDiffPatchInfo对象装入patchList
- 对于Art平台,
checkClassNDexFiles
方法检查data/data/包名/tinker/patch-xxx/dex/tinker_classN.apk是否存在(此文件是Art下合成补丁后的所有new dex打包而成的),判断此补丁是否已经加载过;对于Dalvik平台则不打包tinker_classN.apk,但是也要校验每个Dex是否需要合成 - 遍历patchList开始依次合成dex,调用
patchDexFile
最后通过DexPatchApplier.executeAndSaveTo
合成dex,内部算法实现是DexDiff,合成后dex保存到data/data/包名/tinker/patch-xxx/dex - 每个dex合成完成后都需要校验md5,保证此dex合成正确性,所有dex合成完毕后,Art下需要将所有dex打包成tinker_classN.apk 代码+详细注释:
public class DexDiffPatchInternal extends BasePatchInternal {
......
private static boolean extractDexDiffInternals(Context context, String dir, String meta, File patchFile, int type) {
patchList.clear();
// 解析dex_meta文件,结果装入patchList
ShareDexDiffPatchInfo.parseDexDiffPatchInfo(meta, patchList);
if (patchList.isEmpty()) {
return true;
}
// data/data/包名/tinker/patch-xxx/dex
File directory = new File(dir);
if (!directory.exists()) {
directory.mkdirs();
}
//I think it is better to extract the raw files from apk
Tinker manager = Tinker.with(context);
ZipFile apk = null;
ZipFile patch = null;
try {
ApplicationInfo applicationInfo = context.getApplicationInfo();
if (applicationInfo == null) {
// Looks like running on a test Context, so just return without patching.
ShareTinkerLog.w(TAG, "applicationInfo == null!!!!");
return false;
}
String apkPath = applicationInfo.sourceDir;
// 原apk
apk = new ZipFile(apkPath);
// 补丁包
patch = new ZipFile(patchFile);
// art下合成补丁,合成所有old dex和patch dex,然后打包为tinker_classN.apk,dalvik下不打包dex
// 判断是否需要生成tinker_classN.apk文件
if (checkClassNDexFiles(dir)) {
ShareTinkerLog.w(TAG, "class n dex file %s is already exist, and md5 match, just continue", ShareConstants.CLASS_N_APK_NAME);
return true;
}
for (ShareDexDiffPatchInfo info : patchList) {
long start = System.currentTimeMillis();
final String infoPath = info.path;
String patchRealPath;
if (infoPath.equals("")) {
patchRealPath = info.rawName;
} else {
patchRealPath = info.path + "/" + info.rawName;
}
String dexDiffMd5 = info.dexDiffMd5;
String oldDexCrc = info.oldDexCrC;
// 非主dex,且该dex没有改变的话destMd5InDvm字段值为"0",此dex在dalvik下无需合成
if (!isVmArt && info.destMd5InDvm.equals("0")) {
ShareTinkerLog.w(TAG, "patch dex %s is only for art, just continue", patchRealPath);
continue;
}
String extractedFileMd5 = isVmArt ? info.destMd5InArt : info.destMd5InDvm;
if (!SharePatchFileUtil.checkIfMd5Valid(extractedFileMd5)) {
ShareTinkerLog.w(TAG, "meta file md5 invalid, type:%s, name: %s, md5: %s", ShareTinkerInternals.getTypeString(type), info.rawName, extractedFileMd5);
manager.getPatchReporter().onPatchPackageCheckFail(patchFile, BasePatchInternal.getMetaCorruptedCode(type));
return false;
}
// data/data/包名/tinker/patch-xxx/dex/dex名称,用于存放合成后的dex
File extractedFile = new File(dir + info.realName);
// 检查合成后的dex(此时还未合成,如果存在说明之前已经合成过该dex)是否已经存在,存在说明已经合成过该dex
// 已经存在则要验证是否和补丁包中记录的预合成的dex的md5一致性,不一致需要删除已存在的dex
if (extractedFile.exists()) {
if (SharePatchFileUtil.verifyDexFileMd5(extractedFile, extractedFileMd5)) {
//it is ok, just continue
ShareTinkerLog.w(TAG, "dex file %s is already exist, and md5 match, just continue", extractedFile.getPath());
continue;
} else {
ShareTinkerLog.w(TAG, "have a mismatch corrupted dex " + extractedFile.getPath());
extractedFile.delete();
}
} else {
extractedFile.getParentFile().mkdirs();
}
// 补丁包中patch dex
ZipEntry patchFileEntry = patch.getEntry(patchRealPath);
// old dex
ZipEntry rawApkFileEntry = apk.getEntry(patchRealPath);
if (oldDexCrc.equals("0")) {
// oldDexCrc为"0"表明该dex是新增的
if (patchFileEntry == null) {
ShareTinkerLog.w(TAG, "patch entry is null. path:" + patchRealPath);
manager.getPatchReporter().onPatchTypeExtractFail(patchFile, extractedFile, info.rawName, type);
return false;
}
// 提取补丁包中dex到data/data/包名/tinker/patch-xxx/dex/
if (!extractDexFile(patch, patchFileEntry, extractedFile, info)) {
ShareTinkerLog.w(TAG, "Failed to extract raw patch file " + extractedFile.getPath());
manager.getPatchReporter().onPatchTypeExtractFail(patchFile, extractedFile, info.rawName, type);
return false;
}
} else if (dexDiffMd5.equals("0")) {
// oldDexCrc不为"0",dexDiffMd5为"0"代表该dex没有改变
// 此情况art下需要将old dex拷贝到补丁dex目录,dalvik下忽略
if (!isVmArt) {
continue;
}
if (rawApkFileEntry == null) {
ShareTinkerLog.w(TAG, "apk entry is null. path:" + patchRealPath);
manager.getPatchReporter().onPatchTypeExtractFail(patchFile, extractedFile, info.rawName, type);
return false;
}
//check source crc instead of md5 for faster
String rawEntryCrc = String.valueOf(rawApkFileEntry.getCrc());
// old dex crc校验(补丁包中记录的old dex crc和当前apk中old dex crc)
if (!rawEntryCrc.equals(oldDexCrc)) {
ShareTinkerLog.e(TAG, "apk entry %s crc is not equal, expect crc: %s, got crc: %s", patchRealPath, oldDexCrc, rawEntryCrc);
manager.getPatchReporter().onPatchTypeExtractFail(patchFile, extractedFile, info.rawName, type);
return false;
}
// 从当前apk中将old dex复制到data/data/包名/tinker/patch-xxx/dex/
extractDexFile(apk, rawApkFileEntry, extractedFile, info);
if (!SharePatchFileUtil.verifyDexFileMd5(extractedFile, extractedFileMd5)) {
ShareTinkerLog.w(TAG, "Failed to recover dex file when verify patched dex: " + extractedFile.getPath());
manager.getPatchReporter().onPatchTypeExtractFail(patchFile, extractedFile, info.rawName, type);
SharePatchFileUtil.safeDeleteFile(extractedFile);
return false;
}
} else {
// 此分支中old dex patch dex都应该存在
if (patchFileEntry == null) {
ShareTinkerLog.w(TAG, "patch entry is null. path:" + patchRealPath);
manager.getPatchReporter().onPatchTypeExtractFail(patchFile, extractedFile, info.rawName, type);
return false;
}
if (!SharePatchFileUtil.checkIfMd5Valid(dexDiffMd5)) {
ShareTinkerLog.w(TAG, "meta file md5 invalid, type:%s, name: %s, md5: %s", ShareTinkerInternals.getTypeString(type), info.rawName, dexDiffMd5);
manager.getPatchReporter().onPatchPackageCheckFail(patchFile, BasePatchInternal.getMetaCorruptedCode(type));
return false;
}
// 当前apk中old dex是否存在
if (rawApkFileEntry == null) {
ShareTinkerLog.w(TAG, "apk entry is null. path:" + patchRealPath);
manager.getPatchReporter().onPatchTypeExtractFail(patchFile, extractedFile, info.rawName, type);
return false;
}
// old dex crc校验(补丁包中记录的old dex crc和当前apk中old dex crc)
String rawEntryCrc = String.valueOf(rawApkFileEntry.getCrc());
if (!rawEntryCrc.equals(oldDexCrc)) {
ShareTinkerLog.e(TAG, "apk entry %s crc is not equal, expect crc: %s, got crc: %s", patchRealPath, oldDexCrc, rawEntryCrc);
manager.getPatchReporter().onPatchTypeExtractFail(patchFile, extractedFile, info.rawName, type);
return false;
}
// 合成补丁,合成完后写入合成结果dex到extractedFile(data/data/包名/tinker/patch-xxx/dex)
// 内部通过DexPatchApplier类合成补丁,算法实现相关代码不具体分析
patchDexFile(apk, patch, rawApkFileEntry, patchFileEntry, info, extractedFile);
// 校验本次合成成功后dex的md5,是否和打补丁包时预合成的md5一致
if (!SharePatchFileUtil.verifyDexFileMd5(extractedFile, extractedFileMd5)) {
ShareTinkerLog.w(TAG, "Failed to recover dex file when verify patched dex: " + extractedFile.getPath());
manager.getPatchReporter().onPatchTypeExtractFail(patchFile, extractedFile, info.rawName, type);
SharePatchFileUtil.safeDeleteFile(extractedFile);
return false;
}
ShareTinkerLog.w(TAG, "success recover dex file: %s, size: %d, use time: %d",
extractedFile.getPath(), extractedFile.length(), (System.currentTimeMillis() - start));
}
}
// art将所有合成完毕的dex打包为tinker_classN.apk
if (!mergeClassNDexFiles(context, patchFile, dir)) {
return false;
}
} catch (Throwable e) {
throw new TinkerRuntimeException("patch " + ShareTinkerInternals.getTypeString(type) + " extract failed (" + e.getMessage() + ").", e);
} finally {
SharePatchFileUtil.closeZip(apk);
SharePatchFileUtil.closeZip(patch);
}
return true;
}
}
触发dex2oat编译合成后dex
合成dex成功后需要对每个dex进行dex2oat操作,以免运行时触发dex2oat影响加载速度,tinker借助PathClassLoader触发dex2oat,简略流程如下
DexDiffPatchInternal.dexOptimizeDexFiles
方法做一下预操作,然后调用到TinkerDexOptimizer.optimizeAll
方法,对每个dex/apk/jar文件分别调用OptimizeWorker.run
方法进行dex2oat- android8.0以及以上版本调用
NewClassLoaderInjector.triggerDex2Oat
方法分别通过DelegateLastClassLoader
或者TinkerClassLoader
触发dex2oat,8.0以下直接调用DexFile.loadDex
- 要注意的是android 10以后不再从应用进程调用dex2oat,仅接受系统生成的OAT文件,所以需要通过
triggerPMDexOptOnDemand
方法反射调用pms performDexOptSecondary方法再次尝试触发dex2oat
DexDiffPatchInternal.dexOptimizeDexFiles预操作
根据android版本拼接相应dex2oat输出路径,android8.0后是data/data/包名/tinker/patch-xxx/oat//xxx.odex,8.0前是data/data/包名/tinker/patch-xxx/odex/xxx.dex,路径中isa表示cpu架构,比如arm64
private static boolean dexOptimizeDexFiles(Context context, List<File> dexFiles, String optimizeDexDirectory, final File patchFile, final PatchResult patchResult) {
final Tinker manager = Tinker.with(context);
optFiles.clear();
if (dexFiles != null) {
// data/data/包名/tinker/patch-xxx/odex
File optimizeDexDirectoryFile = new File(optimizeDexDirectory);
if (!optimizeDexDirectoryFile.exists() && !optimizeDexDirectoryFile.mkdirs()) {
return false;
}
// add opt files
for (File file : dexFiles) {
// 获取dexopt产物输出路径
// android8.0后是data/data/包名/tinker/patch-xxx/oat/<isa>/xxx.odex
// 8.0前是data/data/包名/tinker/patch-xxx/odex/xxx.dex
String outputPathName = SharePatchFileUtil.optimizedPathFor(file, optimizeDexDirectoryFile);
optFiles.add(new File(outputPathName));
}
......
// 是否使用DelegateLastClassLoader类
final boolean useDLC = TinkerApplication.getInstance().isUseDelegateLastClassLoader();
final boolean[] anyOatNotGenerated = {false};
// 开始并行dexopt
TinkerDexOptimizer.optimizeAll(
context, dexFiles, optimizeDexDirectoryFile,
useDLC, ......);
......
}
return true;
}
OptimizeWorker.run分版本触发dex2oat
此方法中NewClassLoaderInjector.triggerDex2Oat
函数以及triggerPMDexOptOnDemand
会单独拿出来分析。
另外需要注意useInterpretMode时调用interpretDex2Oat
以解释模式执行dex2oat,这是为了应对系统OTA升级后补丁odex失效的情况,具体会在加载补丁部分细说。
private static class OptimizeWorker {
boolean run() {
try {
......
// android8.0后是data/data/包名/tinker/patch-xxx/oat/<isa>/xxx.odex
// 8.0前是data/data/包名/tinker/patch-xxx/odex/xxx.dex
String optimizedPath = SharePatchFileUtil.optimizedPathFor(this.dexFile, this.optimizedDir);
if (!ShareTinkerInternals.isArkHotRuning()) {
if (useInterpretMode) {
// 以解释模式编译,系统OTA后第一次运行时执行此分支
interpretDex2Oat(dexFile.getAbsolutePath(), optimizedPath);
} else if (Build.VERSION.SDK_INT >= 26
|| (Build.VERSION.SDK_INT >= 25 && Build.VERSION.PREVIEW_SDK_INT != 0)) {
// 通过PathClassLoader/加载dex触发dex2oat
NewClassLoaderInjector.triggerDex2Oat(context, optimizedDir,
useDLC, dexFile.getAbsolutePath());
// https://developer.android.google.cn/about/versions/10/behavior-changes-10?hl=zh-cn#system-only-oat
// android10以后不再从应用进程调用dex2oat,仅接受系统生成的OAT文件
// oat_file_manager.cc OatFileManager::OpenDexFilesFromOat不再调用oat_file_assistant.MakeUpToDate
// 这里通过pms再次触发后台dex2oat
triggerPMDexOptOnDemand(context, dexFile.getAbsolutePath(), optimizedPath);
} else {
// android8.0 之下直接使用DexFile触发dex2oat
DexFile.loadDex(dexFile.getAbsolutePath(), optimizedPath, 0);
}
}
if (callback != null) {
callback.onSuccess(dexFile, optimizedDir, new File(optimizedPath));
}
} catch (final Throwable e) {
if (callback != null) {
callback.onFailed(dexFile, optimizedDir, e);
return false;
}
}
return true;
}
}
通过PathClassLoader触发dex2oat
NewClassLoaderInjector.triggerDex2Oat
调用到NewClassLoaderInjector.createNewClassLoader
,在useDLC=true
并且android版本大于8.1时使用DelegateLastClassLoader触发dex2oat,否则创建TinkerClassLoader触发dex2oat。
实际上触发dex2oat这一步时这两个ClassLoader起到的效果是一样的,最后都是通过DexPathList触发dex2oat,新创建的ClassLoader仅仅用于触发dex2oat。
NewClassLoaderInjector.createNewClassLoader
的另外一个调用处在加载补丁逻辑中,加载补丁时使用新classerLoader替换Application原有PathClassLoader避免android7.0之后以混合编译与对热补丁影响。所以我觉得此方法还是应该分开写成两个函数比较好,以免阅读代码时产生混淆。
此文章中我们只需要知道不管是DelegateLastClassLoader还是TinkerClassLoader作用都是触发dex2oat就好了,对于它们更进一步的分析留到下篇加载补丁时分析。
DelegateLastClassLoader是android8.1后新增的类,继承于PathClassLoader,实行最后查找策略。
- 从boot classpath中查找类
- 从该classLoader的dexPath中查找类
- 最后从该classLoader的双亲中查找类
- 最后查找策略没有遵守双亲委托,最后才从parent classloader中查找类
TinkerClassLoader在这里起的作用也差不多,重写了findClass方法使查找类时先从TinkerClassLoader查找,然后再到原来的PathClassLoader中查找
private static ClassLoader createNewClassLoader(ClassLoader oldClassLoader,
File dexOptDir,
boolean useDLC,
String... patchDexPaths) throws Throwable {
// 反射获取BaseDexClassLoader的DexPathList字段
// old oldClassLoader是当前app的PathClassLoader
final Field pathListField = findField(
Class.forName("dalvik.system.BaseDexClassLoader", false, oldClassLoader),
"pathList");
final Object oldPathList = pathListField.get(oldClassLoader);
final StringBuilder dexPathBuilder = new StringBuilder();
final boolean hasPatchDexPaths = patchDexPaths != null && patchDexPaths.length > 0;
if (hasPatchDexPaths) {
for (int i = 0; i < patchDexPaths.length; ++i) {
if (i > 0) {
dexPathBuilder.append(File.pathSeparator);
}
dexPathBuilder.append(patchDexPaths[i]);
}
}
// 拼接需要dex2oat的dex文件路径
final String combinedDexPath = dexPathBuilder.toString();
// 反射DexPathList中nativeLibraryDirectories字段,so库路径
final Field nativeLibraryDirectoriesField = findField(oldPathList.getClass(), "nativeLibraryDirectories");
......
// 拼接so库路径
final String combinedLibraryPath = libraryPathBuilder.toString();
ClassLoader result = null;
if (useDLC && Build.VERSION.SDK_INT >= 27) {
// https://developer.android.google.cn/reference/dalvik/system/DelegateLastClassLoader
// https://www.androidos.net.cn/android/10.0.0_r6/xref/libcore/dalvik/src/main/java/dalvik/system/DelegateLastClassLoader.java
// DelegateLastClassLoader是android8.1后新增的,继承于PathClassLoader,实行最后查找策略
result = new DelegateLastClassLoader(combinedDexPath, combinedLibraryPath, ClassLoader.getSystemClassLoader());
// 将之前的PathClassLoader设为创建的DelegateLastClassLoader的双亲
final Field parentField = ClassLoader.class.getDeclaredField("parent");
parentField.setAccessible(true);
parentField.set(result, oldClassLoader);
} else {
// 做的事情和DelegateLastClassLoader差不多
result = new TinkerClassLoader(combinedDexPath, dexOptDir, combinedLibraryPath, oldClassLoader);
}
// Android8.0之前版本替换原本的PathClassLoader中PathList中的classLoader为新创建的
// Android 8.0之后不支持多个类加载器同时使用同一个DexFile对象来定义类,所以不能替换
if (Build.VERSION.SDK_INT < 26) {
findField(oldPathList.getClass(), "definingContext").set(oldPathList, result);
}
return result;
}
triggerPMDexOptOnDemand确保在Android10以后能够触发dex2oat
由于android10对于dex2oat策略的变更,这里必须要做进一步处理,tinker主要大概流程如下
- queryPerformDexOptSecondaryTransactionCode方法反射获取用于标识
PackageManagerService.performDexOptSecondary
方法的transaction code - 反射ServiceManager拿到PMS客户端代理IBinder对象
- 通过binder调用
PackageManagerService.performDexOptSecondary
,以quicken模式触发dex2oat
private static void triggerPMDexOptOnDemand(Context context, String dexPath, String oatPath) {
if (Build.VERSION.SDK_INT < 29) {
return;
}
try {
final File oatFile = new File(oatPath);
if (oatFile.exists()) {
return;
}
boolean performDexOptSecondarySuccess = true;
try {
// 调用PMS.performDexOptSecondary触发dex2oat
performDexOptSecondary(context, oatPath);
} catch (Throwable thr) {
performDexOptSecondarySuccess = false;
}
SystemClock.sleep(1000);
if (!performDexOptSecondarySuccess || !oatFile.exists()) {
// 执行失败,如果是华为系统做额外处理
if ("huawei".equalsIgnoreCase(Build.MANUFACTURER) || "honor".equalsIgnoreCase(Build.MANUFACTURER)) {
registerDexModule(context, dexPath, oatPath);
}
}
......
}
public static void performDexOptSecondary(Context context, String oatPath) throws IllegalStateException {
try {
final File oatFile = new File(oatPath);
// 反射获取pms代理用于ipc标记pms.performDexOptSecondary方法的transactionCode
final int transactionCode = queryPerformDexOptSecondaryTransactionCode();
final String packageName = context.getPackageName();
// dex2oat编译模式
final String targetCompilerFilter = "quicken";
final boolean force = false;
final Class<?> serviceManagerClazz = Class.forName("android.os.ServiceManager");
final Method getServiceMethod = ShareReflectUtil.findMethod(serviceManagerClazz, "getService", String.class);
// 拿到PMS远程代理
final IBinder pmBinder = (IBinder) getServiceMethod.invoke(null, "package");
if (pmBinder == null) {
throw new IllegalStateException("Fail to get pm binder.");
}
// 重试20次
final int maxRetries = 20;
for (int i = 0; i < maxRetries; ++i) {
Throwable pendingThr = null;
try {
// 直接通过binder调用pms performDexOptSecondary
performDexOptSecondaryImpl(pmBinder, transactionCode, packageName, targetCompilerFilter, force);
} catch (Throwable thr) {
pendingThr = thr;
}
SystemClock.sleep(3000);
if (oatFile.exists()) {
break;
}
if (i == maxRetries - 1) {
if (pendingThr != null) {
throw pendingThr;
}
if (!oatFile.exists()) {
throw new IllegalStateException("Expected oat file: " + oatFile.getAbsolutePath() + " does not exist.");
}
}
}
......
}
对于PackageManagerService.performDexOptSecondary
方法的实现这里就不再展开了,有兴趣的话可以自行去阅读源码,自此补丁合成过程已分析完毕。
后记
跟着源码一路看下来很明显可以感觉到作者对于framework层的了解,不同android版本中framework的变动对于tinker的影响非常大,厂商对于ROM的定制化修改也会引出一系列问题,解决这些问题实在不算容易的事情。对于一些细节问题我也没有完全了解,如果有错误希望指出。