基于Tinker V1.7.5
- Android 热修复方案Tinker(一) Application改造
- Android 热修复方案Tinker(二) 补丁加载流程
- Android 热修复方案Tinker(三) Dex补丁加载
- Android 热修复方案Tinker(四) 资源补丁加载
- Android 热修复方案Tinker(五) SO补丁加载
- Android 热修复方案Tinker(六) Gradle插件实现
- Android 热修复方案Tinker(七) 插桩实现
- 带注释的源码
这篇文章从加载补丁的入口tryLoad
处开始分析Tinker补丁加载的流程.根据不同的类别Tinker可以支持dex,SO和资源更新,接下来会详细分析.先贴出补丁加载的主要类图.
从tryLoad
入口开始,tryLoad
中调用加载补丁流程的方法,并统计出这次Load Patch所消耗的时间.
Intent resultIntent = new Intent();
//统计 load patch 耗时
long begin = SystemClock.elapsedRealtime();
tryLoadPatchFilesInternal(app, tinkerFlag, tinkerLoadVerifyFlag, resultIntent);
long cost = SystemClock.elapsedRealtime() - begin;
IntentUtil.setIntentPatchCostTime(resultIntent, cost);
return resultIntent;
在tryLoadPatchFilesInternal
中会先做一系列的环境校验,一切就绪之后才真正将加工过的补丁加载到运行环境中.下面按顺序罗列出环境校验的过程.
-
检查tinkerFlag是否设置了enable ? continue : 记录
ERROR_LOAD_DISABLE
到result中,return.if (!ShareTinkerInternals.isTinkerEnabled(tinkerFlag)) { ShareIntentUtil.setIntentReturnCode(resultIntent, ShareConstants.ERROR_LOAD_DISABLE); return; }
-
检查补丁工作空间/tinker是否已经存在 ? continue : 记录
ERROR_LOAD_PATCH_DIRECTORY_NOT_EXIST
到result中,return.File patchDirectoryFile = SharePatchFileUtil.getPatchDirectory(app); if (patchDirectoryFile == null) { Log.w(TAG, "tryLoadPatchFiles:getPatchDirectory == null"); //treat as not exist ShareIntentUtil.setIntentReturnCode(resultIntent, ShareConstants.ERROR_LOAD_PATCH_DIRECTORY_NOT_EXIST); return; } String patchDirectoryPath = patchDirectoryFile.getAbsolutePath(); //检查补丁路径是否存在 if (!patchDirectoryFile.exists()) { Log.w(TAG, "tryLoadPatchFiles:patch dir not exist:" + patchDirectoryPath); ShareIntentUtil.setIntentReturnCode(resultIntent, ShareConstants.ERROR_LOAD_PATCH_DIRECTORY_NOT_EXIST); return; }
-
检查/tinker/patch.info 补丁信息文件是否存在 ? continue : 记录
ERROR_LOAD_PATCH_INFO_NOT_EXIST
到result中,return.//tinker/patch.info File patchInfoFile = SharePatchFileUtil.getPatchInfoFile(patchDirectoryPath); //check patch info file whether exist if (!patchInfoFile.exists()) { Log.w(TAG, "tryLoadPatchFiles:patch info not exist:" + patchInfoFile.getAbsolutePath()); ShareIntentUtil.setIntentReturnCode(resultIntent, ShareConstants.ERROR_LOAD_PATCH_INFO_NOT_EXIST); return; }
-
patch.info文件中存储了如下新旧两个版本补丁的MD5.先用lock文件加锁,然后检查patch info能否读出有效的补丁信息 ? continue : 记录
ERROR_LOAD_PATCH_INFO_CORRUPTED
到result中,return.//old = 641e634c5b8f1649c75caf73794acbdf //new = 2c150d8560334966952678930ba67fa8 File patchInfoLockFile = SharePatchFileUtil.getPatchInfoLockFile(patchDirectoryPath); //通过lockFile加锁, 检查patch info文件中的补丁版本信息 patchInfo = SharePatchInfo.readAndCheckPropertyWithLock(patchInfoFile, patchInfoLockFile); if (patchInfo == null) { ShareIntentUtil.setIntentReturnCode(resultIntent, ShareConstants.ERROR_LOAD_PATCH_INFO_CORRUPTED); return; }
-
检查补丁信息中的数据是否有效? 将信息记录到result中,continue : 记录
ERROR_LOAD_PATCH_INFO_CORRUPTED
到result中,return.String oldVersion = patchInfo.oldVersion; String newVersion = patchInfo.newVersion; if (oldVersion == null || newVersion == null) { //it is nice to clean patch Log.w(TAG, "tryLoadPatchFiles:onPatchInfoCorrupted"); ShareIntentUtil.setIntentReturnCode(resultIntent, ShareConstants.ERROR_LOAD_PATCH_INFO_CORRUPTED); return; } resultIntent.putExtra(ShareIntentUtil.INTENT_PATCH_OLD_VERSION, oldVersion); resultIntent.putExtra(ShareIntentUtil.INTENT_PATCH_NEW_VERSION, newVersion);
-
根据版本变化和是否是主进程的条件决定是否允许加载最新的补丁,并检查该补丁的MD5标识是否不为空 ? continue : 记录
ERROR_LOAD_PATCH_INFO_BLANK
到result中,return.boolean mainProcess = ShareTinkerInternals.isInMainProcess(app); boolean versionChanged = !(oldVersion.equals(newVersion)); //如果版本变化,并且当前运行在主进程,则允许加载最新补丁 String version = oldVersion; if (versionChanged && mainProcess) { version = newVersion; } //检查当前补丁版本的MD5标识是否为空 if (ShareTinkerInternals.isNullOrNil(version)) { Log.w(TAG, "tryLoadPatchFiles:version is blank, wait main process to restart"); ShareIntentUtil.setIntentReturnCode(resultIntent, ShareConstants.ERROR_LOAD_PATCH_INFO_BLANK); return; }
-
检查当前版本补丁路径是否存在 ? continue : 记录
ERROR_LOAD_PATCH_VERSION_DIRECTORY_NOT_EXIST
到result中,return.//patch-641e634c String patchName = SharePatchFileUtil.getPatchVersionDirectory(version); //tinker/patch.info/patch-641e634c String patchVersionDirectory = patchDirectoryPath + "/" + patchName; File patchVersionDirectoryFile = new File(patchVersionDirectory); //检查当前版本补丁路径是否存在 if (!patchVersionDirectoryFile.exists()) { Log.w(TAG, "tryLoadPatchFiles:onPatchVersionDirectoryNotFound"); //we may delete patch info file ShareIntentUtil.setIntentReturnCode(resultIntent, ShareConstants.ERROR_LOAD_PATCH_VERSION_DIRECTORY_NOT_EXIST); return; }
-
检查补丁文件是否存在 ? continue : 记录
ERROR_LOAD_PATCH_VERSION_FILE_NOT_EXIST
到result中,return.//tinker/patch.info/patch-641e634c/patch-641e634c.apk File patchVersionFile = new File(patchVersionDirectoryFile.getAbsolutePath(), SharePatchFileUtil.getPatchVersionFile(version)); //检查补丁文件是否存在 if (!patchVersionFile.exists()) { Log.w(TAG, "tryLoadPatchFiles:onPatchVersionFileNotFound"); //we may delete patch info file ShareIntentUtil.setIntentReturnCode(resultIntent, ShareConstants.ERROR_LOAD_PATCH_VERSION_FILE_NOT_EXIST); return; }
-
检查补丁文件签名和Tinker id是否一致 ? 将补丁的相关信息存入resultIntent,continue : 记录
ERROR_LOAD_PATCH_PACKAGE_CHECK_FAIL
和检查结果到result中,return.ShareSecurityCheck securityCheck = new ShareSecurityCheck(app); //检查补丁文件签名和Tinker id是否一致 int returnCode = ShareTinkerInternals.checkTinkerPackage(app, tinkerFlag, patchVersionFile, securityCheck); if (returnCode != ShareConstants.ERROR_PACKAGE_CHECK_OK) { Log.w(TAG, "tryLoadPatchFiles:checkTinkerPackage"); resultIntent.putExtra(ShareIntentUtil.INTENT_PATCH_PACKAGE_PATCH_CHECK, returnCode); ShareIntentUtil.setIntentReturnCode(resultIntent, ShareConstants.ERROR_LOAD_PATCH_PACKAGE_CHECK_FAIL); return; } resultIntent.putExtra(ShareIntentUtil.INTENT_PATCH_PACKAGE_CONFIG, securityCheck.getPackagePropertiesIfPresent());
这里为了快速校验,就只检验补丁包内部以meta.txt结尾的文件的签名. 其他的文件的合法性则通过校验过的meta.txt文件内部的补丁文件Md5校验.这里只是把meta.txt的内容分别存入到
metaContentMap
中,以供外部使用.while (entries.hasMoreElements()) { JarEntry jarEntry = entries.nextElement(); // no code if (jarEntry == null) { continue; } final String name = jarEntry.getName(); if (name.startsWith("META-INF/")) { continue; } //for faster, only check the meta.txt files //we will check other files's mad5 written in meta files if (!name.endsWith(ShareConstants.META_SUFFIX)) { continue; } metaContentMap.put(name, SharePatchFileUtil.loadDigestes(jarFile, jarEntry)); Certificate[] certs = jarEntry.getCertificates(); if (certs == null) { return false; } if (!check(path, certs)) { return false; } }
根据不同的情况,最多有四个文件是以meta.txt结尾的:
- package_meta.txt 补丁包的基本信息
- dex_meta.txt 所有dex文件的信息
- so_meta.txt 所有so文件的信息
- res_meta.txt 所有资源文件的信息
-
如果支持dex修复 则继续检查dex补丁文件是否存在 ? continue : log, return.
final boolean isEnabledForDex = ShareTinkerInternals.isTinkerEnabledForDex(tinkerFlag); if (isEnabledForDex) { //tinker/patch.info/patch-641e634c/dex boolean dexCheck = TinkerDexLoader.checkComplete(patchVersionDirectory, securityCheck, resultIntent); if (!dexCheck) { //file not found, do not load patch Log.w(TAG, "tryLoadPatchFiles:dex check fail"); return; } }
至于
checkComplete
都校验了哪些东西?可以继续往里面看.先根据第9步中读取到内存中的dex_meta.txt
数据,在parseDexDiffPatchInfo
内部将字符串以dex为单位切割出每个dex文件的详细信息.String meta = securityCheck.getMetaContentMap().get(DEX_MEAT_FILE); //not found dex if (meta == null) { return true; } dexList.clear(); ShareDexDiffPatchInfo.parseDexDiffPatchInfo(meta, dexList); if (dexList.isEmpty()) { return true; }
将dex摘要信息有效的item过滤出来,再遍历过滤出来的dex,去/tinker/patch.info/patch-xxx/dex和/tinker/patch.info/patch-xxx/odex下验证物理文件是否存在.
HashMap<String, String> dexes = new HashMap<>(); for (ShareDexDiffPatchInfo info : dexList) { //for dalvik, ignore art support dex if (isJustArtSupportDex(info)) { continue; } if (!ShareDexDiffPatchInfo.checkDexDiffPatchInfo(info)) { intentResult.putExtra(ShareIntentUtil.INTENT_PATCH_PACKAGE_PATCH_CHECK, ShareConstants.ERROR_PACKAGE_CHECK_DEX_META_CORRUPTED); ShareIntentUtil.setIntentReturnCode(intentResult, ShareConstants.ERROR_LOAD_PATCH_PACKAGE_CHECK_FAIL); return false; } dexes.put(info.realName, info.destMd5InDvm); }
-
如果支持so修复 则继续检查so补丁文件是否存在,校验的方式同第10步.
final boolean isEnabledForNativeLib = ShareTinkerInternals.isTinkerEnabledForNativeLib(tinkerFlag); if (isEnabledForNativeLib) { //tinker/patch.info/patch-641e634c/lib boolean libCheck = TinkerSoLoader.checkComplete(patchVersionDirectory, securityCheck, resultIntent); if (!libCheck) { //file not found, do not load patch Log.w(TAG, "tryLoadPatchFiles:native lib check fail"); return; } }
-
如果支持资源修复 则继续检查资源补丁文件是否存在,校验的方式同第10步.
final boolean isEnabledForResource = ShareTinkerInternals.isTinkerEnabledForResource(tinkerFlag); Log.w(TAG, "tryLoadPatchFiles:isEnabledForResource:" + isEnabledForResource); if (isEnabledForResource) { boolean resourceCheck = TinkerResourceLoader.checkComplete(app, patchVersionDirectory, securityCheck, resultIntent); if (!resourceCheck) { //file not found, do not load patch Log.w(TAG, "tryLoadPatchFiles:resource check fail"); return; } }
-
符合条件的话就更新版本信息,并将最新的patch info更新入文件.在v1.7.5的版本开始有了
isSystemOTA
判断,只要用户是ART环境并且做了OTA升级则在加载dex补丁的时候就会先把最近一次的补丁全部DexFile.loadDex
一遍重新生成odex.再加载dex补丁.//only work for art platform oat boolean isSystemOTA = ShareTinkerInternals.isVmArt() && ShareTinkerInternals.isSystemOTA(patchInfo.fingerPrint); //we should first try rewrite patch info file, if there is a error, we can't load jar if (isSystemOTA || (mainProcess && versionChanged)) { patchInfo.oldVersion = version; //update old version to new if (!SharePatchInfo.rewritePatchInfoFileWithLock(patchInfoFile, patchInfo, patchInfoLockFile)) { ShareIntentUtil.setIntentReturnCode(resultIntent, ShareConstants.ERROR_LOAD_PATCH_REWRITE_PATCH_INFO_FAIL); Log.w(TAG, "tryLoadPatchFiles:onReWritePatchInfoCorrupted"); return; } }
-
检查safe mode计数是否超过三次
if (!checkSafeModeCount(app)) { resultIntent.putExtra(ShareIntentUtil.INTENT_PATCH_EXCEPTION, new TinkerRuntimeException("checkSafeModeCount fail")); ShareIntentUtil.setIntentReturnCode(resultIntent, ShareConstants.ERROR_LOAD_PATCH_UNCAUGHT_EXCEPTION); Log.w(TAG, "tryLoadPatchFiles:checkSafeModeCount fail"); return; }
-
在符合条件的情况下加载dex,res补丁.并记录成功状态.
//now we can load patch jar if (isEnabledForDex) { boolean loadTinkerJars = TinkerDexLoader.loadTinkerJars(app, tinkerLoadVerifyFlag, patchVersionDirectory, resultIntent); if (!loadTinkerJars) { Log.w(TAG, "tryLoadPatchFiles:onPatchLoadDexesFail"); return; } } //now we can load patch resource if (isEnabledForResource) { boolean loadTinkerResources = TinkerResourceLoader.loadTinkerResources(app, tinkerLoadVerifyFlag, patchVersionDirectory, resultIntent); if (!loadTinkerResources) { Log.w(TAG, "tryLoadPatchFiles:onPatchLoadResourcesFail"); return; } } //all is ok! ShareIntentUtil.setIntentReturnCode(resultIntent, ShareConstants.ERROR_LOAD_OK);
接下来详细分析dex,so和资源补丁更新的原理.
转载请注明出处:http://blog.csdn.net/l2show/article/details/53240023