美文网首页
热修复Tinker(二)补丁包加载源码分析

热修复Tinker(二)补丁包加载源码分析

作者: 前世小书童 | 来源:发表于2017-01-15 12:59 被阅读1043次

    写在前面的话

    <p>

    前面一篇Tinker相关的文章已经介绍了Tinker热修复框架的使用与整个的修复流程,那么这一篇就要开启Tinker的源码解析之路了。

    首先简单说一下Tinker的原理,Tinker其实也是类似multidex的dex方式,将目标dex插入到数组最前面,主要是通过对比原dex文件(存在bug)与现dex文件(bug已修复)生成差异包,生成的差异包作为补丁包下发给客户端,客户端做一系列校验之后,将下发的差异包与本应用的dex文件合并成成全量的dex文件,并进行opt优化,当再次启动APP时候则加载优化过的全量dex文件,将dex文件插入到DexPathList 中 dexElements的前面。

    所以Tinker其实是两个流程,一个是加载补丁包,另外一个是加载dex文件,两个的加载流程相对较长,这里分开说明,这一篇呢,主要介绍加载补丁包的流程。

    补丁包加载流程

    <p>

    加载补丁包的方法如下

    TinkerInstaller.onReceiveUpgradePatch(getApplicationContext(), "FilePath");
    

    往下看发现调用了TinkerInstaller的onReceiveUpgradePatch方法

    TinkerInstaller.java

    public static void onReceiveUpgradePatch(Context context, String patchLocation) {
        Tinker.with(context).getPatchListener().onPatchReceived(patchLocation);
    }
    

    这里调用了PatchListener的onPatchReceived方法

    而PatchListener是一个接口,他的具体实现为SamplePatchListener方法,onPatchReceived在SamplePatchListener的父类DefaultPatchListener有实现,我们看下DefaultPatchListener中的onPatchReceived方法
    如下

    DefaultPatchListener.java

    @Override
    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;
    
    }
    

    首先这个检测了一下这个插件是否可用,通过SamplePatchListener的patchCheck方法来检测

    SamplePatchListener.java

    @Override
        public int patchCheck(String path) {
            File patchFile = new File(path);
            TinkerLog.i(TAG, "receive a patch file: %s, file size:%d", path, SharePatchFileUtil.getFileOrDirectorySize(patchFile));
            int returnCode = super.patchCheck(path);
    
            if (returnCode == ShareConstants.ERROR_PATCH_OK) {
                returnCode = Utils.checkForPatchRecover(NEW_PATCH_RESTRICTION_SPACE_SIZE_MIN, maxMemory);
            }
    
            if (returnCode == ShareConstants.ERROR_PATCH_OK) {
                String patchMd5 = SharePatchFileUtil.getMD5(patchFile);
                SharedPreferences sp = context.getSharedPreferences(ShareConstants.TINKER_SHARE_PREFERENCE_CONFIG, Context.MODE_MULTI_PROCESS);
                //optional, only disable this patch file with md5
                int fastCrashCount = sp.getInt(patchMd5, 0);
                if (fastCrashCount >= SampleUncaughtExceptionHandler.MAX_CRASH_COUNT) {
                    returnCode = Utils.ERROR_PATCH_CRASH_LIMIT;
                } else {
                    //for upgrade patch, version must be not the same
                    //for repair patch, we won't has the tinker load flag
                    Tinker tinker = Tinker.with(context);
    
                    if (tinker.isTinkerLoaded()) {
                        TinkerLoadResult tinkerLoadResult = tinker.getTinkerLoadResultIfPresent();
                        if (tinkerLoadResult != null) {
                            String currentVersion = tinkerLoadResult.currentVersion;
                            if (patchMd5.equals(currentVersion)) {
                                returnCode = Utils.ERROR_PATCH_ALREADY_APPLY;
                            }
                        }
                    }
                }
                //check whether retry so many times
                if (returnCode == ShareConstants.ERROR_PATCH_OK) {
                    returnCode = UpgradePatchRetry.getInstance(context).onPatchListenerCheck(patchMd5)
                        ? ShareConstants.ERROR_PATCH_OK : Utils.ERROR_PATCH_RETRY_COUNT_LIMIT;
                }
            }
            // Warning, it is just a sample case, you don't need to copy all of these
            // Interception some of the request
            if (returnCode == ShareConstants.ERROR_PATCH_OK) {
                Properties properties = ShareTinkerInternals.fastGetPatchPackageMeta(patchFile);
                if (properties == null) {
                    returnCode = Utils.ERROR_PATCH_CONDITION_NOT_SATISFIED;
                } else {
                    String platform = properties.getProperty(Utils.PLATFORM);
                    TinkerLog.i(TAG, "get platform:" + platform);
                    // check patch platform require
                    if (platform == null || !platform.equals(BuildInfo.PLATFORM)) {
                        returnCode = Utils.ERROR_PATCH_CONDITION_NOT_SATISFIED;
                    }
                }
            }
    
            SampleTinkerReport.onTryApply(returnCode == ShareConstants.ERROR_PATCH_OK);
            return returnCode;
        }
    
    

    这里对插件是否可用进行了判断,就不进行详细分析了

    当插件可用时候returnCode为ERROR_PATCH_OK,当不可用则会log出来失败的errorcode

    成功则调用

     TinkerPatchService.runPatchService(context, path);
    

    来启动TinkerPatchService这个IntentService,并且把插件的路径给传递到IntentService

    TinkerPatchService通过onHandleIntent来接收传递过来的数据

    TinkerPatchService.java

     @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));
    
        }
    
    

    这里首先调用了PatchReporter的onPatchServiceStart方法,而PatchReporter的实现为SamplePatchReporter

    SamplePatchReporter.java

       @Override
        public void onPatchServiceStart(Intent intent) {
            super.onPatchServiceStart(intent);
            SampleTinkerReport.onApplyPatchServiceStart();
            UpgradePatchRetry.getInstance(context).onPatchServiceStart(intent);
        }
    
    

    这里主要看UpgradePatchRetry的onPatchServiceStart方法

    UpgradePatchRetry.java

        public void onPatchServiceStart(Intent intent) {
            if (!isRetryEnable) {
                TinkerLog.w(TAG, "onPatchServiceStart retry disabled, just return");
                return;
            }
    
            if (intent == null) {
                TinkerLog.e(TAG, "onPatchServiceStart intent is null, just return");
                return;
            }
    
            String path = TinkerPatchService.getPatchPathExtra(intent);
    
            if (path == null) {
                TinkerLog.w(TAG, "onPatchServiceStart patch path is null, just return");
                return;
            }
    
            RetryInfo retryInfo;
            File patchFile = new File(path);
    
            String patchMd5 = SharePatchFileUtil.getMD5(patchFile);
            if (patchMd5 == null) {
                TinkerLog.w(TAG, "onPatchServiceStart patch md5 is null, just return");
                return;
            }
    
            if (retryInfoFile.exists()) {
                retryInfo = RetryInfo.readRetryProperty(retryInfoFile);
                if (retryInfo.md5 == null || retryInfo.times == null || !patchMd5.equals(retryInfo.md5)) {
                    copyToTempFile(patchFile);
                    retryInfo.md5 = patchMd5;
                    retryInfo.times = "1";
                } else {
                    int nowTimes = Integer.parseInt(retryInfo.times);
                    if (nowTimes >= RETRY_MAX_COUNT) {
                        SharePatchFileUtil.safeDeleteFile(tempPatchFile);
                        TinkerLog.w(TAG, "onPatchServiceStart retry more than max count, delete retry info file!");
                        return;
                    } else {
                        retryInfo.times = String.valueOf(nowTimes + 1);
                    }
                }
    
            } else {
                copyToTempFile(patchFile);
                retryInfo = new RetryInfo(patchMd5, "1");
            }
    
            RetryInfo.writeRetryProperty(retryInfoFile, retryInfo);
        }
    
    

    这里主要也做了一些验证,并且把文件复制一份到/data/data/tinker.sample.android/tinker_temp/路径下,然后把相关信息写入到配置文件中

    在回到TinkerPatchService的onHandleIntent方法

    主要看

    result = upgradePatchProcessor.tryPatch(context, path, patchResult);
    
    

    这个方法的实现在UpgradePatch中

    UpgradePatch.java

        @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 (!patchFile.isFile() || !patchFile.exists()) {
                TinkerLog.e(TAG, "UpgradePatch tryPatch:patch file is not found, just return");
                return false;
            }
            //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;
            }
    
            //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 (oldInfo.oldVersion.equals(patchMd5) || oldInfo.newVersion.equals(patchMd5)) {
                    TinkerLog.e(TAG, "UpgradePatch tryPatch:onPatchVersionCheckFail");
                    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 {
                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;
            }
    
            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;
            }
    
            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;
        }
    
    }
    
    

    这里首先初始化相关数据与相关验证,再将补丁文件拷贝到目标目录中

    SharePatchFileUtil.copyFileUsingStream(patchFile, destPatchFile);
    
    

    路径为/data/data/tinker.sample.android/tinker/patch-xxxxxx/patch-xxxxxx.apk

    接下来就是调用DexDiffPatchInternal,BsDiffPatchInternal,ResDiffPatchInternal这些类的方法进行dexDiff差分的计算相关

    至于相关差分的计算,由于比较复杂,我暂时还没有深入去看,暂时埋个坑在这里,等后面找时间去填上这个坑

    在回到TinkerPatchService的onHandleIntent方法

    后面调用了PatchReporter的onPatchResult,这个方法主要删除了上面拷贝在/data/data/tinker.sample.android/tinker_temp/的文件

    接下来启动了AbstractResultService,并把插件的路径传递过去了

    AbstractResultService的实现在SampleResultService类里面,SampleResultService的onPatchResult删除了原始的插件文件。

    到这里插件加载分析就基本结束了

    写在后面的话

    <p>

    插件加载分析结束了,但是却没有去分析dexDiff差分的计算,而这个dexDiff差分计算则是区分的Tinker与其他相同方案的热修复库,dexDiff是基于 Dex 的文件结构来下手,将产生变化的结构提取出来,产生的补丁非常小,而且在 diff 的过程中也处理了一些会造成补丁包很大的场景,所以等后面有时间将这一块补上,下一篇文章则是对dex文件加载进行源码分析了,peace~~~

    相关文章

      网友评论

          本文标题:热修复Tinker(二)补丁包加载源码分析

          本文链接:https://www.haomeiwen.com/subject/xdpybttx.html