Tinker源码分析(六):补丁合成流程

2019-03-19 17:14:11 浏览数 (1)

本系列 Tinker 源码解析基于 Tinker v1.9.12

补丁合成流程

下发的补丁包其实并不能直接加载,因为补丁包只是差异包,需要和本地的 dex 、资源等进行合成后,得到全量的 dex 才能被完整地使用。这样也就避免了热修复中 dex 的 pre-verify 问题,也减少了补丁包的体积,方便用户下载。

补丁合成的入口在 TinkerInstaller.onReceiveUpgradePatch 方法

TinkerInstaller.onReceiveUpgradePatch

代码语言:javascript复制
public static void onReceiveUpgradePatch(Context context, String patchLocation) {
    Tinker.with(context).getPatchListener().onPatchReceived(patchLocation);
}

这里的 PatchListener 有默认实现类,即 DefaultPatchListener 。

DefaultPatchListener.onPatchReceived

代码语言:javascript复制
@Override
public int onPatchReceived(String path) {
    File patchFile = new File(path);
    // 对补丁进行校验
    int returnCode = patchCheck(path, SharePatchFileUtil.getMD5(patchFile));

    if (returnCode == ShareConstants.ERROR_PATCH_OK) {
        // 通过的话就启动 :process 进程进行补丁合成    
        TinkerPatchService.runPatchService(context, path);
    } else {
        // 校验失败就回调 onLoadPatchListenerReceiveFail
        Tinker.with(context).getLoadReporter().onLoadPatchListenerReceiveFail(new File(path), returnCode);
    }
    return returnCode;
}

我们直接来看 TinkerPatchService.runPatchService 方法

TinkerPatchService.runPatchService

代码语言:javascript复制
public static void runPatchService(final Context context, final String path) {
    TinkerLog.i(TAG, "run patch service...");
    Intent intent = new Intent(context, TinkerPatchService.class);
    // path 就是补丁的路径
    intent.putExtra(PATCH_PATH_EXTRA, path);
    // RESULT_CLASS_EXTRA 一般默认就是 DefaultTinkerResultService
    intent.putExtra(RESULT_CLASS_EXTRA, resultServiceClass.getName());
    try {
        enqueueWork(context, TinkerPatchService.class, JOB_ID, intent);
    } catch (Throwable thr) {
        TinkerLog.e(TAG, "run patch service fail, exception:"   thr);
    }
}

在 runPatchService 中去启动了 TinkerPatchService 。TinkerPatchService 是跑在 :patch 进程中的。

TinkerPatchService 主要做的事情都在 onHandleWork 中

代码语言:javascript复制
@Override
protected void onHandleWork(Intent intent) {
    // 提高优先级
    increasingPriority();
    // 合成补丁
    doApplyPatch(this, intent);
}

首先是 increasingPriority 方法,目的就是提高 service 的优先级,具体的方案就是设置为前台服务

代码语言:javascript复制
private void increasingPriority() {
    if (Build.VERSION.SDK_INT >= 26) {
        TinkerLog.i(TAG, "for system version >= Android O, we just ignore increasingPriority "
                  "job to avoid crash or toasts.");
        return;
    }

    if ("ZUK".equals(Build.MANUFACTURER)) {
        TinkerLog.i(TAG, "for ZUK device, we just ignore increasingPriority "
                  "job to avoid crash.");
        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);
    }
}

接着是 doApplyPatch 方法,在这里做补丁合成的事

代码语言:javascript复制
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)) {
        TinkerLog.w(TAG, "TinkerPatchService doApplyPatch is running by another runner.");
        return;
    }

    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;

    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;
    // 补丁合成的结果回调给 DefaultResultService
    AbstractResultService.runResultService(context, patchResult, getPatchResultExtra(intent));

    sIsPatchApplying.set(false);
}

upgradePatchProcessor 是一个接口,具体的实现类是 UpgradePatch 。

UpgradePatch.tryPatch

那么来看看 UpgradePatch.tryPatch ,方法比较长,分段来看吧。

首先是对 Tinker 自身开关的校验,然后对补丁文件的合法性进行校验。

代码语言:javascript复制
@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)) {
        TinkerLog.e(TAG, "UpgradePatch tryPatch:patch is disabled, just return");
        return false;
    }

    if (!SharePatchFileUtil.isLegalFile(patchFile)) {
        TinkerLog.e(TAG, "UpgradePatch tryPatch:patch file is not found, just return");
        return false;
    }

然后检查补丁包的签名以及 tinkerId , 这里的操作和加载补丁是一样的。

然后就是获取补丁文件的 md5 值

代码语言:javascript复制
//check the signature, we should create a new checker
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;
}

// 获取补丁文件的 md5
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
// 用 md5 做版本号
patchResult.patchVersion = patchMd5;

TinkerLog.i(TAG, "UpgradePatch tryPatch:patchMd5:%s", patchMd5);

接着,校验完成后,我们就来构造一个新的 patch.info 文件了。

代码语言:javascript复制
//check ok, we can real recover a new patch
final String patchDirectory = manager.getPatchDirectory().getAbsolutePath();

// info.lock 文件
File patchInfoLockFile = SharePatchFileUtil.getPatchInfoLockFile(patchDirectory);
// patch.info 文件
File patchInfoFile = SharePatchFileUtil.getPatchInfoFile(patchDirectory);

// 读取出老的 patch.info 文件,可能存在 可能不存在
SharePatchInfo oldInfo = SharePatchInfo.readAndCheckPropertyWithLock(patchInfoFile, patchInfoLockFile);

//it is a new patch, so we should not find a exist
SharePatchInfo newInfo;

//如果有老的 patch.info 文件
if (oldInfo != null) {
    if (oldInfo.oldVersion == null || oldInfo.newVersion == null || oldInfo.oatDir == 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;
    }
    // if it is interpret now, use changing flag to wait main process
    final String finalOatDir = oldInfo.oatDir.equals(ShareConstants.INTERPRET_DEX_OPTIMIZE_PATH)
        ? ShareConstants.CHANING_DEX_OPTIMIZE_PATH : oldInfo.oatDir;
    // 构造新的 patch.info 
    newInfo = new SharePatchInfo(oldInfo.oldVersion, patchMd5, false, Build.FINGERPRINT, finalOatDir);
} else {
    // 构造新的 patch.info 
    newInfo = new SharePatchInfo("", patchMd5, false, Build.FINGERPRINT, ShareConstants.DEFAULT_DEX_OPTIMIZE_PATH);
}

再接下来,就是把补丁包复制到私有目录中

具体的路径也就是之前在加载补丁中遇到的 /data/data/应用包名/tinker/patch-xxxxxx/patch-xxxxxx.apk

代码语言:javascript复制
//it is a new patch, we first delete if there is any files
//don't delete dir for faster retry
//        SharePatchFileUtil.deleteDir(patchVersionDirectory);
final String patchName = SharePatchFileUtil.getPatchVersionDirectory(patchMd5);

final String patchVersionDirectory = patchDirectory   "/"   patchName;

TinkerLog.i(TAG, "UpgradePatch tryPatch:patchVersionDirectory:%s", patchVersionDirectory);

//copy file
File destPatchFile = new File(patchVersionDirectory   "/"   SharePatchFileUtil.getPatchVersionFile(patchMd5));

try {
  // check md5 first
  if (!patchMd5.equals(SharePatchFileUtil.getMD5(destPatchFile))) {
      // 复制补丁包到 /data/data/ 中
      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) {
//            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;
}

复制好之后,就是把补丁包和基准包进行整合了

代码语言:javascript复制
//we use destPatchFile instead of patchFile, because patchFile may be deleted during the patch process
// 合成 dex
if (!DexDiffPatchInternal.tryRecoverDexFiles(manager, signatureCheck, context, patchVersionDirectory, destPatchFile)) {
    TinkerLog.e(TAG, "UpgradePatch tryPatch:new patch recover, try patch dex failed");
    return false;
}
// 合成 so 文件
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;
}

这里面的三个合成代码我们到后面的章节再分析,这里先跳过了。

合成完后,还要对 dex 进行opt优化

代码语言:javascript复制
// 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;
}

最后,就是把结果重新写入到 patch.info ,这样在加载补丁的流程中就能加载新补丁了。

代码语言:javascript复制
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;

over ,整个合成补丁的流程讲完了,这里还留了三个坑:

  • dex 文件的合成
  • so 文件的合成
  • 资源文件的合成

到后面再讲吧。

0 人点赞