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

先要配置这么一段话:后面会谈到为什么以及如何改变这些默认设置

    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来让整个程序可以继续进行(一般加载完补丁会有些界面上的调整)

发布了223 篇原创文章 · 获赞 23 · 访问量 18万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章