热修复框架Tinker与Robust原理剖析
热修复框架Tinker与Robust原理剖析
一、热修复技术概述
1.1 什么是热修复
热修复(Hot Fix)是Android平台上的一种动态修复机制,它允许应用在不重新发布版本的情况下,动态修复线上bug。这种技术对于快速修复线上问题、降低用户流失率具有重要意义。
1.2 热修复的应用场景
- 紧急bug修复
- 功能动态更新
- A/B测试
- 动态功能控制
1.3 主流热修复方案对比
| 方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| Tinker | 支持全量更新、性能好 | 补丁包较大、需要重启生效 | 大型应用 |
| Robust | 补丁包小、实时生效 | 需要预留空间 | 小型应用 |
二、热修复基本原理
2.1 类加载机制
public class PathClassLoader extends BaseDexClassLoader {public PathClassLoader(String dexPath, ClassLoader parent) {super(dexPath, null, null, parent);}
}
Android的类加载机制是热修复实现的基础,主要涉及以下几个方面:
- DexClassLoader工作原理
- 双亲委派模型
- 类加载时机控制
2.2 Dex分包方案
public class HotFixManager {private static void loadDex(Context context, File patchFile) {try {// 获取当前应用的ClassLoaderClassLoader classLoader = context.getClassLoader();// 获取DexPathList属性Field pathListField = findField(classLoader, "pathList");Object pathList = pathListField.get(classLoader);// 获取dexElements属性Field dexElementsField = findField(pathList, "dexElements");Object[] dexElements = (Object[]) dexElementsField.get(pathList);// 加载补丁dex文件DexClassLoader patchLoader = new DexClassLoader(patchFile.getAbsolutePath(),context.getCacheDir().getAbsolutePath(),null,classLoader);// 合并dex数组Object[] patchElements = getDexElements(patchLoader);Object[] newElements = (Object[]) Array.newInstance(dexElements.getClass().getComponentType(),dexElements.length + patchElements.length);System.arraycopy(patchElements, 0, newElements, 0, patchElements.length);System.arraycopy(dexElements, 0, newElements, patchElements.length, dexElements.length);// 将新的dexElements数组设置回去dexElementsField.set(pathList, newElements);} catch (Exception e) {e.printStackTrace();}}
}
2.3 资源热修复
public class ResourcePatcher {public static void patch(Context context, String patchApkPath) {try {// 创建新的AssetManagerAssetManager newAssetManager = AssetManager.class.newInstance();// 加载补丁包资源Method addAssetPath = AssetManager.class.getDeclaredMethod("addAssetPath", String.class);addAssetPath.setAccessible(true);addAssetPath.invoke(newAssetManager, patchApkPath);// 替换原有的AssetManagerField assetManagerField = Activity.class.getDeclaredField("mAssets");assetManagerField.setAccessible(true);assetManagerField.set(context, newAssetManager);} catch (Exception e) {e.printStackTrace();}}
}
三、Tinker实现原理
3.1 核心机制
Tinker采用了全量替换的方式进行热修复,主要包括以下几个方面:
- Dex差量算法
- 资源差量算法
- So库差量算法
需要注意的是,由于Tinker采用类替换方案,且Android中类加载是不可逆的,所以补丁需要重启应用后才能生效。这是因为已加载的类无法被卸载或重新加载,只有在应用重启后,新的类加载过程中才能加载到补丁中的新类。
3.1.1 类替换机制详解
public class TinkerDexLoader {private static void loadNewDex(Application application, File patchDexFile) {try {// 1. 获取当前应用的PathClassLoaderPathClassLoader pathClassLoader = (PathClassLoader) application.getClassLoader();// 2. 获取DexPathList对象Object dexPathList = ReflectUtil.getField(pathClassLoader, "pathList");// 3. 获取原有的dexElements数组Object[] oldDexElements = (Object[]) ReflectUtil.getField(dexPathList, "dexElements");// 4. 加载补丁dex文件List<File> files = new ArrayList<>();files.add(patchDexFile);Object[] patchDexElements = makeDexElements(files);// 5. 合并dex数组,补丁dex放在前面Object[] newDexElements = (Object[]) Array.newInstance(oldDexElements.getClass().getComponentType(),oldDexElements.length + patchDexElements.length);System.arraycopy(patchDexElements, 0, newDexElements, 0, patchDexElements.length);System.arraycopy(oldDexElements, 0, newDexElements, patchDexElements.length, oldDexElements.length);// 6. 将新的dexElements数组设置回去ReflectUtil.setField(dexPathList, "dexElements", newDexElements);} catch (Exception e) {e.printStackTrace();}}// 创建dexElements数组private static Object[] makeDexElements(List<File> files) throws Exception {// 获取DexPathList.makeDexElements方法Method makeDexElements = Class.forName("dalvik.system.DexPathList").getDeclaredMethod("makeDexElements", List.class, File.class, List.class);makeDexElements.setAccessible(true);// 调用makeDexElements方法创建dex数组ArrayList<IOException> suppressedExceptions = new ArrayList<>();return (Object[]) makeDexElements.invoke(null, files, null, suppressedExceptions);}
}
类替换机制的核心原理是利用Android的类加载机制。当一个类需要被加载时,ClassLoader会按顺序遍历dexElements数组中的DexFile,找到第一个包含该类的DexFile进行加载。通过将补丁dex放在数组前面,可以优先加载补丁中的类,从而实现类的替换。
3.1.2 资源替换机制
public class TinkerResourceLoader {public static void loadNewResources(Context context, String patchApkPath) {try {// 1. 创建新的AssetManagerAssetManager newAssetManager = AssetManager.class.newInstance();// 2. 反射调用addAssetPath方法加载补丁资源Method addAssetPath = AssetManager.class.getDeclaredMethod("addAssetPath", String.class);addAssetPath.setAccessible(true);addAssetPath.invoke(newAssetManager, patchApkPath);// 3. 获取Resources对象Resources oldResources = context.getResources();Resources newResources = new Resources(newAssetManager,oldResources.getDisplayMetrics(),oldResources.getConfiguration());// 4. 替换应用的Resources对象ReflectUtil.setField(context, "mResources", newResources);// 5. 替换Activity的Resources对象for (Activity activity : ActivityManager.getActivities()) {ReflectUtil.setField(activity, "mResources", newResources);}} catch (Exception e) {e.printStackTrace();}}
}
资源替换机制通过创建新的AssetManager并加载补丁包中的资源,然后替换应用中的Resources对象来实现。这样,当应用访问资源时,就会使用补丁包中的新资源。
3.2 集成步骤
// app/build.gradle
apply plugin: 'com.tencent.tinker.patch'dependencies {implementation "com.tencent.tinker:tinker-android-lib:${TINKER_VERSION}"annotationProcessor "com.tencent.tinker:tinker-android-anno:${TINKER_VERSION}"
}tinkerPatch {oldApk = "${bakPath}/app-release.apk"ignoreWarning = falseuseSign = true
}
3.3 应用实例
@DefaultLifeCycle(application = "com.example.MyApplication")
public class SampleApplication extends TinkerApplication {public SampleApplication() {super(ShareConstants.TINKER_ENABLE_ALL,"com.example.SampleApplicationLike","com.tencent.tinker.loader.TinkerLoader",false);}
}public class SampleApplicationLike extends DefaultApplicationLike {@Overridepublic void onBaseContextAttached(Context base) {super.onBaseContextAttached(base);// 初始化TinkerTinkerManager.installTinker(this);}
}
四、Robust实现原理
4.1 核心机制
Robust采用了插桩方式实现热修复,主要特点:
- 方法级别的修复
- 实时生效
- 补丁包小
Robust通过在编译期间对每个方法进行插桩,在运行时通过动态代理机制实现方法的替换,这种设计使得补丁可以立即生效,无需重启应用。这是因为方法的调用会实时判断是否存在补丁,而不依赖于类的加载机制。
4.1.1 插桩原理详解
// 原始代码
public class UserManager {public String getUserName(int userId) {return "User:" + userId;}
}// 插桩后的代码
public class UserManager {public String getUserName(int userId) {if (PatchProxy.isSupport(new Object[]{userId}, this, false)) {return (String) PatchProxy.accessDispatch(new Object[]{userId}, this, false);}return "User:" + userId;}
}// 补丁代码
public class UserManagerPatch implements IPatch {public String getUserName(int userId) {return "Fixed User:" + userId;}
}
Robust的插桩过程主要包括以下步骤:
- 在编译期间,通过Gradle插件对需要支持热修复的方法进行插桩
- 在每个方法前插入PatchProxy.isSupport判断是否需要执行补丁代码
- 如果存在补丁,通过PatchProxy.accessDispatch调用补丁中的新方法
4.1.2 方法替换实现
public class PatchProxy {private static Map<String, IPatch> patchMap = new HashMap<>();public static boolean isSupport(Object[] params, Object current, boolean isStatic) {String className = current.getClass().getName();return patchMap.containsKey(className);}public static Object accessDispatch(Object[] params, Object current, boolean isStatic) {try {String className = current.getClass().getName();IPatch patch = patchMap.get(className);// 通过反射调用补丁中的方法String methodName = Thread.currentThread().getStackTrace()[2].getMethodName();Method patchMethod = patch.getClass().getDeclaredMethod(methodName, getParameterTypes(params));return patchMethod.invoke(patch, params);} catch (Exception e) {e.printStackTrace();return null;}}public static void installPatch(String className, IPatch patch) {patchMap.put(className, patch);}
}
方法替换的核心是通过PatchProxy类来管理和分发方法调用。当应用运行时:
- 加载补丁时,通过installPatch方法注册补丁类
- 执行插桩后的方法时,先通过isSupport检查是否存在补丁
- 如果存在补丁,通过accessDispatch方法反射调用补丁中的新方法
- 如果不存在补丁或执行失败,则执行原方法
这种方式可以实现方法级别的热修复,并且补丁包只需要包含修改的方法,大小较小。同时,由于是在方法调用时进行判断和替换,所以能够实时生效。
4.2 集成步骤
// app/build.gradle
apply plugin: 'com.robust.gradle.plugin'dependencies {implementation 'com.meituan.robust:robust:0.4.99'
}robust {enable = trueenableLog = trueexceptPackage = ['com.meituan.sample.except'] //不需要插桩的包名
}
4.3 应用实例
public class RobustCallBack implements PatchExecutor.RobustCallBack {@Overridepublic void onPatchApplied(boolean success, String message) {// 补丁应用回调}
}public class MainActivity extends AppCompatActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);// 加载补丁new PatchExecutor(getApplicationContext(), new PatchManipulateImp(), new RobustCallBack()).start();}
}
五、实战案例
5.1 线上Bug修复流程
- 定位问题代码
- 修改代码生成补丁
- 下发补丁
- 验证修复效果
5.2 最佳实践
- 补丁包大小控制
- 加载时机选择
- 兜底方案准备
- 灰度发布策略
5.3 注意事项
- 混淆规则配置
- 类修改限制
- 版本适配问题
- 内存占用控制
六、面试题解析
6.1 热门面试题
-
Q:Android热修复的原理是什么?
A:主要基于类加载机制,通过替换ClassLoader中的DexElements数组,使得新的类优先加载,从而实现代码的热修复。 -
Q:Tinker和Robust的区别是什么?
A:- Tinker采用全量替换方案,支持代码、资源、So库的修复,补丁包较大
- Robust采用插桩方案,只支持方法级别的修复,补丁包小,实时生效
-
Q:如何解决热修复方案的兼容性问题?
A:- 针对不同Android版本做适配
- 做好机型适配测试
- 设置补丁加载的降级策略
- 保留线上紧急更新渠道
七、开源项目分析
7.1 微信Tinker
GitHub地址:https://github.com/Tencent/tinker
核心特性:
- 支持代码、资源、So库的修复
- 高性能的差量算法
- 完善的监控体系
7.2 美团Robust
GitHub地址:https://github.com/Meituan-Dianping/Robust
核心特性:
- 高兼容性
- 实时生效
- 补丁包小
八、总结
本文详细介绍了Android热修复技术的原理和实现方案,重点分析了Tinker和Robust两个主流框架的特点和使用方法。通过学习本文内容,读者应该能够:
- 理解热修复的基本原理
- 掌握主流框架的使用方法
- 能够根据实际场景选择合适的方案
- 了解热修复技术的最佳实践
本文介绍的热修复技术是Android应用开发中的重要工具,掌握这项技术将帮助你更好地处理线上问题,提供更好的用户体验。如果你有任何问题,欢迎在评论区讨论交流。
相关文章:
热修复框架Tinker与Robust原理剖析
热修复框架Tinker与Robust原理剖析 一、热修复技术概述 1.1 什么是热修复 热修复(Hot Fix)是Android平台上的一种动态修复机制,它允许应用在不重新发布版本的情况下,动态修复线上bug。这种技术对于快速修复线上问题、降低用户流…...
69.Harmonyos NEXT图片预览组件应用实践(二):电商、内容与办公场景
温馨提示:本篇博客的详细代码已发布到 git : https://gitcode.com/nutpi/HarmonyosNext 可以下载运行哦! Harmonyos NEXT图片预览组件应用实践(二):电商、内容与办公场景 文章目录 Harmonyos NEXT图片预览组件应用实践…...
C题库-判断水仙花数
【数据判断】 问题1:判断水仙花数,水仙花数是指一个三位数,其各位数字的立方和等于该数本身。 方法一: #include<stdio.h>int main(void){int num,Bit,Ten,Hundred;printf("Input a number:");scanf("%d&q…...
31.Harmonyos Next仿uv-ui 组件NumberBox 步进器组件异步操作处理
Harmonyos Next仿uv-ui 组件NumberBox 步进器组件异步操作处理 文章目录 Harmonyos Next仿uv-ui 组件NumberBox 步进器组件异步操作处理1. 组件介绍2. 效果展示3. 异步操作处理3.1 异步初始化3.2 异步值更新 4. 完整示例代码5. 知识点讲解5.1 异步操作基础5.2 异步操作中的状态…...
MIPI电平标准详解
一、MIPI电平的定义与核心特性 MIPI(Mobile Industry Processor Interface) 是由 MIPI联盟 制定的移动设备接口标准,涵盖摄像头(CSI)、显示屏(DSI)、射频(RFFE)等多个领…...
使用位运算实现加法、减法、乘法和除法
使用位运算实现加法、减法、乘法和除法是一个经典的计算机科学问题。位运算通常用于低级程序设计和性能优化中,以下是如何用位运算实现这些基本数学运算。 加法 加法可以通过以下步骤实现: def add(a, b):while b ! 0:# 使用异或得到不考虑进位的加法…...
深入解析Go语言Channel:源码剖析与并发读写机制
文章目录 Channel的内部结构Channel的创建过程有缓冲Channel的并发读写机制同时读写的可能性发送操作的实现接收操作的实现 并发读写的核心机制解析互斥锁保护环形缓冲区等待队列直接传递优化Goroutine调度 实例分析:有缓冲Channel的并发读写性能优化与最佳实践缓冲…...
mac安装python没有环境变量怎么办?zsh: command not found: python
在mac电脑上,下载Python安装包进行安装之后,在终端中,输入python提示: zsh: command not found: python 一、原因分析 首先,这个问题不是因为python没有安装成功的原因,是因为python安装的时候,没有为我们添加环境变量导致的,所以我们只需要,在.zshrc配置文件中加上环…...
使用DeepSeek制作可视化图表和流程图
用DeepSeek来制作可视化图表,搭配python、mermaid、html来实现可视化,我已经测试过好几种场景,都能实现自动化的代码生成,效果还是不错的,流程如下。 统计图表 (搭配Matplotlib来做) Python中的…...
jmeter-sample
jmeter-sample http request:接口测试常用请求参数ParametersBody DataFiles Upload jdbc request配置JDBC Connection Configuration创建JDBC Requst请求 http request:接口测试常用 请求参数 Parameters 常见于get请求,与拼在接口后面是一样的效果:如…...
C++之文字修仙小游戏
1 效果 1.1 截图 游戏运行: 存档: 1.2 游玩警告 注意!不要修改装备概率,装备的概率都是凑好的数字。如果想要速升,修改灵石数量 2 代码 2.1 代码大纲 1. 游戏框架与初始化 控制台操作:通过 gotoxy() …...
C++ vector 核心知识:常用操作与示例详解
在C编程中,vector 是标准模板库(STL)中最常用的容器之一。它以其动态数组的特性、高效的尾部操作和便捷的随机访问能力,成为处理动态数据的首选工具。无论是初学者还是经验丰富的开发者,掌握 vector 的使用方法和性能优…...
结构型模式之适配器模式:让不兼容的接口兼容
在软件开发中,经常会遇到这样一种情况:系统的不同部分需要进行交互,但由于接口不兼容,导致无法直接使用。这时,适配器模式(Adapter Pattern)就能派上用场。适配器模式是设计模式中的结构型模式&…...
从零开始探索C++游戏开发:性能、控制与无限可能
一、为何选择C开发游戏? 在虚幻引擎5渲染的次世代画面背后,在《巫师3》的庞大开放世界中,在《毁灭战士》的丝滑60帧战斗里,C始终扮演着核心技术角色。这门诞生于1983年的语言,至今仍占据着游戏引擎开发语言使用率榜首…...
使用mvn archetype命令,构建自定义springboot archetype脚手架创建工程的方法
使用mvn archetype命令,构建自定义springboot archetype脚手架创建工程的方法 文章目录 使用mvn archetype命令,构建自定义springboot archetype脚手架创建工程的方法一、背景二、环境三、archetype插件配置四、基于项目构建脚手架archetype包五、检查模…...
Hutool RedisDS:Java开发中的Redis极简集成与高阶应用
在Java开发中,Redis作为高性能内存数据库,广泛应用于缓存、分布式锁等场景。然而原生的客户端操作涉及连接管理、序列化等繁琐细节。Hutool工具包提供的RedisDS模块,通过高度封装显著简化了这一过程。本文从实战角度解析其核心特性与使用技巧…...
MacOS 15.3.1 安装 GPG 提示Error: unknown or unsupported macOS version: :dunno
目录 1. 问题锁定 2. 更新 Homebrew 3. 切换到新的 Homebrew 源 4. 安装 GPG 5. 检查 macOS 版本兼容性 6. 使用 MacPorts 或其他包管理器 7. 创建密钥(生成 GPG 签名) 往期推荐 1. 问题锁定 通常是因为你的 Homebrew 版本较旧,或者你…...
Sqlmap注入工具简单解释
安装 1. 安装 Python SQLMap 是基于 Python 开发的,所以要先安装 Python 环境。建议安装 Python 3.9 或更高版本,可从 Python 官方网站 下载对应操作系统的安装包,然后按照安装向导完成安装。 2. 获取 SQLMap 可以从 SQLMap 的官方 GitHu…...
硬件驱动——51单片机:独立按键、中断、定时器/计数器
目录 一、独立按键 1.原理 2.封装函数 3.按键控制点灯 数码管 二、中断 1.原理 2.步骤 3.中断寄存器IE 4.控制寄存器TCON 5.打开外部中断0和1 三、定时器/计数器 1.原理 2.控制寄存器TCON 3.工作模式寄存器TMOD 4.按键控制频率的动态闪烁 一、独立按键 1…...
语文-文言文
文章目录 短歌行归园田居梦游天姥吟留别 / 别东鲁诸公 学习文言文,适当个人分析; 短歌行 曹操 对酒当歌,人生几何(主题,人生很短暂); 譬如朝露,去日苦多(比喻,…...
P1259 黑白棋子的移动【java】【AC代码】
有 2n 个棋子排成一行,开始为位置白子全部在左边,黑子全部在右边,如下图为 n5 的情况: 移动棋子的规则是:每次必须同时移动相邻的两个棋子,颜色不限,可以左移也可以右移到空位上去,但…...
【极光 Orbit·STC8AH】04. 深度探索 GPIO 底层逻辑
【极光 OrbitSTC8A&H】04. 深度探索 GPIO 底层逻辑 引言:当代码遇见硬件 上周我看着学生调试的工控产品,他们困惑地盯着自己编写的代码:“老师,这段C语言明明在PC上跑得没问题啊!” ,让我想起自己初学…...
67.Harmonyos NEXT 图片预览组件之性能优化策略
温馨提示:本篇博客的详细代码已发布到 git : https://gitcode.com/nutpi/HarmonyosNext 可以下载运行哦! Harmonyos NEXT 图片预览组件之性能优化策略 文章目录 Harmonyos NEXT 图片预览组件之性能优化策略效果预览一、性能优化概述1. 性能优化的关键指标…...
uni-app+SpringBoot: 前端传参,后端如何接收参数
做项目中的一些小经验,方便后续 (1)前端代码中,请求的 URL 是通过查询参数(?id${articleId})传递的 后端接口: GetMapping("/knowledgeDetail") public Result getKnowledgeByid(R…...
【Vue.js】
一、简介 1、概述 官网GitHub - Vuejs Vue 是一款用于构建用户界面的 JavaScript 框架。它基于标准 HTML、CSS 和 JavaScript 构建,并提供了一套声明式的、组件化的编程模型,帮助你高效地开发用户界面。 Vue.js作为一个渐进式框架,其设计理…...
正则表达式入门及常用的正则表达式
正则表达式(Regular Expression,简称 Regex)是一种强大的文本处理工具,用于匹配、查找和替换字符串中的特定模式。以下是入门指南和常用正则表达式示例: 一、正则表达式入门 1. 基本语法 符号说明示例.匹配任意单个字…...
Windows下安装Git客户端
① 官网地址:https://git-scm.com/。 ② Git的优势 大部分操作在本地完成,不需要联网;完整性保证;尽可能添加数据而不是删除或修改数据;分支操作非常快捷流畅;与Linux 命令全面兼容。 ③ Git的安装 从官网…...
SAP IBP for Supply Chain Certification Guide (Parag Bakde, Rishabh Gupta)
SAP IBP for Supply Chain Certification Guide (Parag Bakde, Rishabh Gupta)...
【计算机网络通信 AMQP】使用 Qt 调用 qamqp 库进行 AMQP 通信
以下是一个使用 Qt 实现 AMQP 通信的代码示例。为了实现这个功能,我们可以使用 qamqp 库,它是一个基于 Qt 的 AMQP 客户端库。首先,你需要将 qamqp 库添加到你的 Qt 项目中,可以通过 qmake 或 CMake 进行配置。 #include <QCo…...
C语言中的指针与数组:概念、关系与应用
指针和数组都是C语言中极其重要的概念,本文将分步骤深入分析指针和数组在C语言中的概念、它们之间的关系以及它们在实际编程中的应用。 一、指针与数组的基本概念详解 1.1 指针详解 指针是一个变量,它存储的是另一个变量的内存地址。理解指针的核心就是“内存地址”,指针…...
