Android 热修复方案Tinker(二) 补丁加载流程

2023-11-22 11:30

本文主要是介绍Android 热修复方案Tinker(二) 补丁加载流程,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

转载来源http://blog.csdn.net/l2show/article/details/53240023


这篇文章从加载补丁的入口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;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

tryLoadPatchFilesInternal中会先做一系列的环境校验,一切就绪之后才真正将加工过的补丁加载到运行环境中.下面按顺序罗列出环境校验的过程.

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

            if (!ShareTinkerInternals.isTinkerEnabled(tinkerFlag)) {ShareIntentUtil.setIntentReturnCode(resultIntent, ShareConstants.ERROR_LOAD_DISABLE);return;}
    • 1
    • 2
    • 3
    • 4
    • 1
    • 2
    • 3
    • 4
  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 existShareIntentUtil.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;}
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
  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;}
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
  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;}
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
  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 patchLog.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);
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
  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;}
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
  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 fileShareIntentUtil.setIntentReturnCode(resultIntent, ShareConstants.ERROR_LOAD_PATCH_VERSION_DIRECTORY_NOT_EXIST);return;}
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
  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 fileShareIntentUtil.setIntentReturnCode(resultIntent, ShareConstants.ERROR_LOAD_PATCH_VERSION_FILE_NOT_EXIST);return;}
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
  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());
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    这里为了快速校验,就只检验补丁包内部以meta.txt结尾的文件的签名. 其他的文件的合法性则通过校验过的meta.txt文件内部的补丁文件Md5校验.这里只是把meta.txt的内容分别存入到metaContentMap中,以供外部使用.

            while (entries.hasMoreElements()) {JarEntry jarEntry = entries.nextElement();// no codeif (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 filesif (!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;}}
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    根据不同的情况,最多有四个文件是以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/dexboolean dexCheck = TinkerDexLoader.checkComplete(patchVersionDirectory, securityCheck, resultIntent);if (!dexCheck) {//file not found, do not load patchLog.w(TAG, "tryLoadPatchFiles:dex check fail");return;}}
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    至于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;}
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    将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 dexif (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);}
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
  11. 如果支持so修复 则继续检查so补丁文件是否存在,校验的方式同第10步.

            final boolean isEnabledForNativeLib = ShareTinkerInternals.isTinkerEnabledForNativeLib(tinkerFlag);if (isEnabledForNativeLib) {//tinker/patch.info/patch-641e634c/libboolean libCheck = TinkerSoLoader.checkComplete(patchVersionDirectory, securityCheck, resultIntent);if (!libCheck) {//file not found, do not load patchLog.w(TAG, "tryLoadPatchFiles:native lib check fail");return;}}
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
  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 patchLog.w(TAG, "tryLoadPatchFiles:resource check fail");return;}}
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
  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 newif (!SharePatchInfo.rewritePatchInfoFileWithLock(patchInfoFile, patchInfo, patchInfoLockFile)) {ShareIntentUtil.setIntentReturnCode(resultIntent, ShareConstants.ERROR_LOAD_PATCH_REWRITE_PATCH_INFO_FAIL);Log.w(TAG, "tryLoadPatchFiles:onReWritePatchInfoCorrupted");return;}}
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
  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;}
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
  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);
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

接下来详细分析dex,so和资源补丁更新的原理.


这篇关于Android 热修复方案Tinker(二) 补丁加载流程的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/409841

相关文章

在Android平台上实现消息推送功能

《在Android平台上实现消息推送功能》随着移动互联网应用的飞速发展,消息推送已成为移动应用中不可或缺的功能,在Android平台上,实现消息推送涉及到服务端的消息发送、客户端的消息接收、通知渠道(... 目录一、项目概述二、相关知识介绍2.1 消息推送的基本原理2.2 Firebase Cloud Me

Java图片压缩三种高效压缩方案详细解析

《Java图片压缩三种高效压缩方案详细解析》图片压缩通常涉及减少图片的尺寸缩放、调整图片的质量(针对JPEG、PNG等)、使用特定的算法来减少图片的数据量等,:本文主要介绍Java图片压缩三种高效... 目录一、基于OpenCV的智能尺寸压缩技术亮点:适用场景:二、JPEG质量参数压缩关键技术:压缩效果对比

SpringBoot首笔交易慢问题排查与优化方案

《SpringBoot首笔交易慢问题排查与优化方案》在我们的微服务项目中,遇到这样的问题:应用启动后,第一笔交易响应耗时高达4、5秒,而后续请求均能在毫秒级完成,这不仅触发监控告警,也极大影响了用户体... 目录问题背景排查步骤1. 日志分析2. 性能工具定位优化方案:提前预热各种资源1. Flowable

Android中Dialog的使用详解

《Android中Dialog的使用详解》Dialog(对话框)是Android中常用的UI组件,用于临时显示重要信息或获取用户输入,本文给大家介绍Android中Dialog的使用,感兴趣的朋友一起... 目录android中Dialog的使用详解1. 基本Dialog类型1.1 AlertDialog(

Spring Boot 配置文件之类型、加载顺序与最佳实践记录

《SpringBoot配置文件之类型、加载顺序与最佳实践记录》SpringBoot的配置文件是灵活且强大的工具,通过合理的配置管理,可以让应用开发和部署更加高效,无论是简单的属性配置,还是复杂... 目录Spring Boot 配置文件详解一、Spring Boot 配置文件类型1.1 applicatio

Java进行文件格式校验的方案详解

《Java进行文件格式校验的方案详解》这篇文章主要为大家详细介绍了Java中进行文件格式校验的相关方案,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录一、背景异常现象原因排查用户的无心之过二、解决方案Magandroidic Number判断主流检测库对比Tika的使用区分zip

Android Kotlin 高阶函数详解及其在协程中的应用小结

《AndroidKotlin高阶函数详解及其在协程中的应用小结》高阶函数是Kotlin中的一个重要特性,它能够将函数作为一等公民(First-ClassCitizen),使得代码更加简洁、灵活和可... 目录1. 引言2. 什么是高阶函数?3. 高阶函数的基础用法3.1 传递函数作为参数3.2 Lambda

mss32.dll文件丢失怎么办? 电脑提示mss32.dll丢失的多种修复方法

《mss32.dll文件丢失怎么办?电脑提示mss32.dll丢失的多种修复方法》最近,很多电脑用户可能遇到了mss32.dll文件丢失的问题,导致一些应用程序无法正常启动,那么,如何修复这个问题呢... 在电脑常年累月的使用过程中,偶尔会遇到一些问题令人头疼。像是某个程序尝试运行时,系统突然弹出一个错误提

IDEA中Git版本回退的两种实现方案

《IDEA中Git版本回退的两种实现方案》作为开发者,代码版本回退是日常高频操作,IntelliJIDEA集成了强大的Git工具链,但面对reset和revert两种核心回退方案,许多开发者仍存在选择... 目录一、版本回退前置知识二、Reset方案:整体改写历史1、IDEA图形化操作(推荐)1.1、查看提

电脑提示找不到openal32.dll文件怎么办? openal32.dll丢失完美修复方法

《电脑提示找不到openal32.dll文件怎么办?openal32.dll丢失完美修复方法》openal32.dll是一种重要的系统文件,当它丢失时,会给我们的电脑带来很大的困扰,很多人都曾经遇到... 在使用电脑过程中,我们常常会遇到一些.dll文件丢失的问题,而openal32.dll的丢失是其中比较