基于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中会先做一系列的环境校验,一切就绪之后才真正将加工过的补丁加载到运行环境中.下面按顺序罗列出环境校验的过程.

  1. 检查tinkerFlag是否设置了enable ? continue : 记录ERROR_LOAD_DISABLE到result中,return.

    if (!ShareTinkerInternals.isTinkerEnabled(tinkerFlag)) {    ShareIntentUtil.setIntentReturnCode(resultIntent, ShareConstants.ERROR_LOAD_DISABLE);    return;}
  2. 检查补丁工作空间/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;}
  3. 检查/tinker/patch.info 补丁信息文件是否存在 ? continue : 记录ERROR_LOAD_PATCH_INFO_NOT_EXIST到result中,return.

    //tinker/patch.infoFile patchInfoFile = SharePatchFileUtil.getPatchInfoFile(patchDirectoryPath);//check patch info file whether existif (!patchInfoFile.exists()) {    Log.w(TAG, "tryLoadPatchFiles:patch info not exist:" + patchInfoFile.getAbsolutePath());    ShareIntentUtil.setIntentReturnCode(resultIntent, ShareConstants.ERROR_LOAD_PATCH_INFO_NOT_EXIST);    return;}
  4. patch.info文件中存储了如下新旧两个版本补丁的MD5.先用lock文件加锁,然后检查patch info能否读出有效的补丁信息 ? continue : 记录ERROR_LOAD_PATCH_INFO_CORRUPTED到result中,return.

    //old = 641e634c5b8f1649c75caf73794acbdf//new = 2c150d8560334966952678930ba67fa8File 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;}
  5. 检查补丁信息中的数据是否有效? 将信息记录到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);
  6. 根据版本变化和是否是主进程的条件决定是否允许加载最新的补丁,并检查该补丁的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;}
  7. 检查当前版本补丁路径是否存在 ? continue : 记录ERROR_LOAD_PATCH_VERSION_DIRECTORY_NOT_EXIST到result中,return.

    //patch-641e634cString patchName = SharePatchFileUtil.getPatchVersionDirectory(version);//tinker/patch.info/patch-641e634cString 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;}
  8. 检查补丁文件是否存在 ? continue : 记录ERROR_LOAD_PATCH_VERSION_FILE_NOT_EXIST到result中,return.

    //tinker/patch.info/patch-641e634c/patch-641e634c.apkFile 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;}
  9. 检查补丁文件签名和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 所有资源文件的信息
  10. 如果支持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 dexif (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 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);}
  11. 如果支持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;    }}
  12. 如果支持资源修复 则继续检查资源补丁文件是否存在,校验的方式同第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;    }}
  13. 符合条件的话就更新版本信息,并将最新的patch info更新入文件.在v1.7.5的版本开始有了isSystemOTA判断,只要用户是ART环境并且做了OTA升级则在加载dex补丁的时候就会先把最近一次的补丁全部DexFile.loadDex一遍重新生成odex.再加载dex补丁.

    //only work for art platform oatboolean isSystemOTA = ShareTinkerInternals.isVmArt() && ShareTinkerInternals.isSystemOTA(patchInfo.fingerPrint);//we should first try rewrite patch info file, if there is a error, we can't load jarif (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;    }}
  14. 检查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;}
  15. 在符合条件的情况下加载dex,res补丁.并记录成功状态.

    //now we can load patch jarif (isEnabledForDex) {    boolean loadTinkerJars = TinkerDexLoader.loadTinkerJars(app, tinkerLoadVerifyFlag, patchVersionDirectory, resultIntent);    if (!loadTinkerJars) {        Log.w(TAG, "tryLoadPatchFiles:onPatchLoadDexesFail");        return;    }}//now we can load patch resourceif (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

更多相关文章

  1. Android(安卓)热修复方案Tinker(五) SO补丁加载
  2. Android(安卓)自动播放音频 / Android(安卓)auto play audio
  3. android 布局文件详解
  4. Android(安卓)Studio:一个空格导致的错误
  5. radio button 旁边放图片以及强制程序本地化以及string含有参数
  6. Android(安卓)设置背景透明
  7. android listview custom style 自定义样式
  8. 【转】Android(安卓)xml资源文件中@、@android:type、@*、?、@+含
  9. Android(安卓)xml资源文件中@、@android:type、@*、?、@+含义和区

随机推荐

  1. android 重启
  2. Android ButterKnife 使用方法总结
  3. 自定义控件:滑动开关按钮
  4. xamarin.forms 使用ZXing扫描二维码
  5. Android懒人框架Android annotation
  6. Android(安卓)Camera API使用指南
  7. 第三方社交网站分享(微信,新浪微博)
  8. Android(安卓)拍照7.0适配(源码)
  9. android 自定义控件基础之三种约束类型
  10. Android(安卓)Jni , 不同 arm 架构 兼容