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

来源:互联网 发布:域名转让流程 编辑:程序博客网 时间:2024/06/05 15:37

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

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

原创粉丝点击