美团robust框架
- 先获取美团包名
adb shell pm list packages | findstr meituan
- 查找包名位置
adb shell pm path com.sankuai.meituan
- 拉取包名
adb pull 目标.apk 目标保存位置
使用jadx编译工具打开apk文件,随便打开一个类文件如下:
package com.meituan.hotel.android.hplus.diagnoseTool;
import com.meituan.android.paladin.Paladin;
import com.meituan.robust.ChangeQuickRedirect;
import com.meituan.robust.PatchProxy;
/* JADX INFO: loaded from: classes8.dex */
public final class a {
/* JADX INFO: renamed from: a, reason: collision with root package name */
public static a f78232a; // 原对象
public static ChangeQuickRedirect changeQuickRedirect; // 热修复标记
static {
Paladin.record(1275720172492966010L);
}
public static synchronized a a() {
Object[] objArr = new Object[0]; // 这里表示的是该方法的入参,因为这个方法没用入参,所以是空数组
ChangeQuickRedirect changeQuickRedirect2 = changeQuickRedirect;
if (PatchProxy.isSupport(objArr, null, changeQuickRedirect2, 5484360)) {
return (a) PatchProxy.accessDispatch(objArr, null, changeQuickRedirect2, 5484360);
}
if (f78232a == null) {
f78232a = new a();
}
return f78232a;
}
}
因为开了混淆,所以代码逻辑比较难看明白,不过不重要,热更新的关键代码已经有了
if (PatchProxy.isSupport(objArr, null, changeQuickRedirect2, 5484360)) {
return (a) PatchProxy.accessDispatch(objArr, null, changeQuickRedirect2, 5484360);
}
可以看到这个同步方法在执行的时候,会通过这个if判断,如果满足条件就会把当前的参数传递给PatchProxy去执行注入的钩子方法,以实现热修复拦截。

其原始代码可能如下:
// 原始代码(开发者写的)一个简单的单例模式
public class Singleton {
private static Singleton instance;
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
程序员只需要写正常的代码即可,Robust插件会在编译时自动为每个类添加上面例子中的changeQuickRedirect静态变量,并为每个方法的前面插入像例子中那样的if判断逻辑。
当App上线后发现有Bug,可以下发一个补丁包。你的App下载这个补丁包后,框架会通过DexClassLoader加载它,并利用反射技术,把changeQuickRedirect这个静态变量指向补丁包中实现了新逻辑的对象。这样,下次再执行到这个方法时,就会自动“绕道”去执行补丁里的新代码了。
由于不需要重新加载 Class,只需要替换一个静态变量的值。所以可以做到实时更新,不需要重启APP就可以生效。
其实最关键的就是,如何在运行期,修改changeQuickRedirect静态变量,和补丁方法的注入。

具体代码可以看
PatchExecutor和PatchManipulateImpl,PatchManipulateImpl负责加载补丁包, PatchExecutor负责创建DexClassLoader加载这个补丁包,这两个类都没有被混淆,可以放心查看😄。DexClassLoader 可以加载外部存储的 JAR/APK/DEX 文件,这是 Android 热修复的基础能力。加载后,补丁包中的类就可以被访问了,可以通过反射修改对应类的字段信息。
//PatchExecutor
public boolean patchClass(Context context, Patch patch) {
PatchesInfo patchesInfo;
Object objNewInstance;
ClassLoader patchClassLoader = PatchManager.getPatchClassLoader(patch);
if (patchClassLoader == null) {
try {
patchClassLoader = new DexClassLoader(patch.getTempPath(), PatchManager.getCurrentProcessPatchCacheDir(context).getAbsolutePath(), null, PatchExecutor.class.getClassLoader());
} catch (Throwable th) {
// do something
}
}
try {
patch.getPatchesInfoImplClassFullName();
// 使用DexClassLoader加载补丁修复方法列表
patchesInfo = (PatchesInfo) patchClassLoader.loadClass(patch.getPatchesInfoImplClassFullName()).newInstance();
} catch (Throwable th2) {
// do something
}
List<PatchedClassInfo> patchedClassesInfo = patchesInfo.getPatchedClassesInfo();
int i = 0;
for (PatchedClassInfo patchedClassInfo : patchedClassesInfo) {
String str = patchedClassInfo.patchedClassName; // 目标类名
String str2 = patchedClassInfo.patchClassName; // 补丁类名
if (!TextUtils.isEmpty(str) && !TextUtils.isEmpty(str2)) {
try {
try {
Class<?> clsLoadClass = patchClassLoader.loadClass(str.trim());
Field field = clsLoadClass.getField(Constants.INSERT_FIELD_NAME); // 尝试直接获取名为 "changeQuickRedirect" 的字段
if (field == null) {
// 这里是为了避免直接获取字段获取不到(可能因混淆等原因导致),再遍历fields 通过ChangeQuickRedirect类型进行匹配。
Field[] declaredFields = clsLoadClass.getDeclaredFields();
while (true) {
Field field2 = declaredFields[i2];
if (TextUtils.equals(field2.getType().getCanonicalName(), ChangeQuickRedirect.class.getCanonicalName()) && TextUtils.equals(field2.getDeclaringClass().getCanonicalName(), clsLoadClass.getCanonicalName())) {
field = field2;
break;
}
}
}
if (field == null) {
// do something
} else {
try {
objNewInstance = patchClassLoader.loadClass(str2).newInstance();
field.setAccessible(true);
} catch (Throwable th4) {
th = th4;
}
try {
// 关键赋值!将补丁实例赋值给目标类的静态 changeQuickRedirect 字段
// 第一个参数 null 表示这是静态字段(不需要传入具体对象实例)
field.set(null, objNewInstance);
} catch (Throwable th5) {
}
}
} catch (Throwable th6) {
}
} catch (ClassNotFoundException e2) {
}
} else {
}
}
return i == patchedClassesInfo.size();
}
到此,补丁类已经加载完毕,再看下调用执行时时怎么执行的。
/**
* 类PatchProxy
* @param objArr 方法入参
* @param obj 原对象指针,传入null表示这个是静态方法
* @param changeQuickRedirect 补丁代理对象
* @param i 被替换的方法ID,使用方法ID定位具体的方法,避免混淆导致方法名称对应不上的问题
* @return 是否支持
*/
public static boolean isSupport(Object[] objArr, Object obj, ChangeQuickRedirect changeQuickRedirect, int i) {
boolean z;
if (changeQuickRedirect == null) {
return false;
}
if (obj == null) {
z = true;
} else {
z = false;
}
String classMethod = getClassMethod(z, String.valueOf(i));
if (isEmpty(classMethod)) {
return false;
}
try {
return changeQuickRedirect.isSupport(classMethod, getObjects(objArr, obj, z)); // 根据入参判断是否有适合的方法执行
} catch (Throwable unused) {
return false;
}
}
具体执行则是最终调用PatchProxy.accessDispatch -->changeQuickRedirect.accessDispatch
public interface ChangeQuickRedirect {
Object accessDispatch(String str, Object[] objArr);
boolean isSupport(String str, Object[] objArr);
}
到这里其实基本就明白了美团Robust框架是如何基于方法插桩实现热修复的,通过插桩插件在编译期向每一个方法插入一段拦截代码。
其实这种方案的优缺点也很明显。
- 优点是实时生效,无需重启,且基于方法,框架执行的时候自动插桩,兼容性高。
- 缺点则是增加了包apk的体积(虽然支持根据配置自行选择哪些方法需要插桩,但是写代码的时候一般都没人能敢保证不会有bug吧😁),由于基本每个方法都会插桩,所以导致程序运行时都会执行判断补丁的逻辑,导致运行有一定的性能损耗,且不支持资源文件的替换。
微信Tinker热更新框架
相比上面的robust框架,Tinker 的核心思路是:不直接在原 Dex 上打补丁,而是将基准 Dex 与补丁包合成一个完整的新 Dex,然后替换旧的。

同样的,先pull下微信的apk文件
adb shell pm list packages | grep tencent
adb shell pm path com.tencent.mm
adb pull /data/app/com.tencent.mm-xxx/base.apk E:\apks\wechat\
tinker框架的相关核心代码都在com.tencent.tinker.loader目录下。
package com.tencent.mm.app;
import android.content.Context;
import com.tencent.tinker.loader.app.TinkerApplication;
/* JADX INFO: loaded from: classes9.dex */
public class Application extends TinkerApplication {
private static final String TINKER_LOADER_ENTRY_CLASSNAME = "com.tencent.tinker.loader.TinkerLoader";
private static final String WECHAT_APPLICATION_LIKE_CLASSNAME = "com.tencent.mm.app.MMApplicationLike";
private boolean mIsAttachBaseContextDone;
private final boolean[] mIsDisallowedToCallGetBaseContextInAttachBaseContext;
public Application() {
// 7 = 1 (DEX) + 2 (RESOURCE) + 4 (LIBRARY),表示同时支持 Dex、资源和 So 库的热修复。
// WECHAT_APPLICATION_LIKE_CLASSNAME 指定了业务代理类 MMApplicationLike,Tinker 会在合适的时机(如 attachBaseContext、onCreate)回调这个类中的对应方法。
// 指定了 Tinker 的加载器,这是执行补丁加载任务的入口。
// false:表示在加载补丁时,不进行签名校验, 第一个true :开启安全模式。如果补丁加载导致应用连续崩溃,安全模式会自动禁用补丁。第二个true:开启补丁加载结果的检查,如果加载失败会记录相关信息,便于排查问题
super(7, WECHAT_APPLICATION_LIKE_CLASSNAME, TINKER_LOADER_ENTRY_CLASSNAME, false, true, true);
this.mIsAttachBaseContextDone = false;
this.mIsDisallowedToCallGetBaseContextInAttachBaseContext = new boolean[]{false};
}
public Context _doNotCallThisMethodUnlessYouNeedToGetBaseContextForHacking() {
return super.getBaseContext();
}
@Override // com.tencent.tinker.loader.app.TinkerApplication, android.content.ContextWrapper
public void attachBaseContext(Context context) {
// // 调用父类,触发 Tinker 的补丁加载流程
super.attachBaseContext(context);
this.mIsAttachBaseContextDone = true;
}
@Override // android.content.ContextWrapper, android.content.Context
public Context getApplicationContext() {
return this;
}
@Override // com.tencent.tinker.loader.app.TinkerApplication, android.content.ContextWrapper
public Context getBaseContext() {
if (this.mIsAttachBaseContextDone || !isDisallowedToCallGetBaseContextInAttachBaseContext()) {
return super.getBaseContext();
}
throw new UnsupportedOperationException("please don't call app.getBaseContext(), use app itself directly would be fine in most cases.");
}
public boolean isDisallowedToCallGetBaseContextInAttachBaseContext() {
boolean z16;
synchronized (this.mIsDisallowedToCallGetBaseContextInAttachBaseContext) {
z16 = this.mIsDisallowedToCallGetBaseContextInAttachBaseContext[0];
}
return z16;
}
public void markDisallowToCallGetBaseContextInAttachBaseContext() {
synchronized (this.mIsDisallowedToCallGetBaseContextInAttachBaseContext) {
this.mIsDisallowedToCallGetBaseContextInAttachBaseContext[0] = true;
}
}
}
这是微信的application入口,他其实做的事很少,完全将热更新的操作交给了父类TinkerApplication。
-
TINKER_LOADER_ENTRY_CLASSNAME:这是 Tinker 框架的入口。父类会通过反射创建这个类的实例,来执行具体的补丁加载任务。 -
WECHAT_APPLICATION_LIKE_CLASSNAME:这是微信自己真正的“Application”逻辑所在。因为继承了 TinkerApplication 的类不能被热修复,所以微信将所有业务初始化代码都迁移到了 MMApplicationLike 这个代理类中。当补丁加载完成后,Tinker 框架会通过回调,让 MMApplicationLike 中的新逻辑生效。
public abstract class TinkerApplication extends Application {
private void loadTinker() {
try {
Class<?> cls = Class.forName(this.loaderClassName, false, TinkerApplication.class.getClassLoader());
this.tinkerResultIntent = (Intent) cls.getMethod(TINKER_LOADER_METHOD, TinkerApplication.class).invoke(cls.getConstructor(new Class[0]).newInstance(new Object[0]), this);
} catch (Throwable th6) {
Intent intent = new Intent();
this.tinkerResultIntent = intent;
ShareIntentUtil.setIntentReturnCode(intent, -20);
this.tinkerResultIntent.putExtra("intent_patch_exception", th6);
}
}
@Override // android.content.ContextWrapper
public void attachBaseContext(Context context) {
super.attachBaseContext(context);
long jElapsedRealtime = SystemClock.elapsedRealtime();
long jCurrentTimeMillis = System.currentTimeMillis();
Thread.setDefaultUncaughtExceptionHandler(new TinkerUncaughtHandler(this));
onBaseContextAttached(context, jElapsedRealtime, jCurrentTimeMillis); // 继续调用具体的加载方法
}
public void onBaseContextAttached(Context context, long j16, long j17) {
try {
loadTinker();
this.mCurrentClassLoader = context.getClassLoader();
Handler handlerCreateInlineFence = createInlineFence(this, this.tinkerFlags, this.delegateClassName, this.tinkerLoadVerifyFlag, j16, j17, this.tinkerResultIntent); // 创建WECHAT_APPLICATION_LIKE_CLASSNAME的代理,用于生命周期的各事件回调触发
this.mInlineFence = handlerCreateInlineFence;
TinkerInlineFenceAction.callOnBaseContextAttached(handlerCreateInlineFence, context);
if (this.useSafeMode) {
ShareTinkerInternals.setSafeModeCount(this, 0);
}
} catch (TinkerRuntimeException e16) {
throw e16;
} catch (Throwable th6) {
throw new TinkerRuntimeException(th6.getMessage(), th6);
}
}
private void loadTinker() {
try {
// loaderClassName对应TINKER_LOADER_ENTRY_CLASSNAME
Class<?> cls = Class.forName(this.loaderClassName, false, TinkerApplication.class.getClassLoader());
// 调用TINKER_LOADER_ENTRY_CLASSNAME定义的Loader类中的tryload方法加载补丁
this.tinkerResultIntent = (Intent) cls.getMethod(TINKER_LOADER_METHOD, TinkerApplication.class).invoke(cls.getConstructor(new Class[0]).newInstance(new Object[0]), this);
} catch (Throwable th6) {
Intent intent = new Intent();
this.tinkerResultIntent = intent;
ShareIntentUtil.setIntentReturnCode(intent, -20);
this.tinkerResultIntent.putExtra("intent_patch_exception", th6);
}
}
private Handler createInlineFence(Application application, int i16, String str, boolean z16, long j16, long j17, Intent intent) {
try {
Class<?> cls = Class.forName(str, false, this.mCurrentClassLoader);
Class<?> cls2 = Long.TYPE;
Object objNewInstance = cls.getConstructor(Application.class, Integer.TYPE, Boolean.TYPE, cls2, cls2, Intent.class).newInstance(application, Integer.valueOf(i16), Boolean.valueOf(z16), Long.valueOf(j16), Long.valueOf(j17), intent);
Constructor<?> constructor = Class.forName("com.tencent.tinker.entry.TinkerApplicationInlineFence", false, this.mCurrentClassLoader).getConstructor(Class.forName("com.tencent.tinker.entry.ApplicationLike", false, this.mCurrentClassLoader));
constructor.setAccessible(true);
return (Handler) constructor.newInstance(objNewInstance);
} catch (Throwable th6) {
throw new TinkerRuntimeException("createInlineFence failed", th6);
}
}
@Override // android.content.ContextWrapper, android.content.Context
public Resources getResources() {
Resources resources = super.getResources();
Handler handler = this.mInlineFence;
// 防御性判空,返回tinker加载的替换资源
return handler == null ? resources : TinkerInlineFenceAction.callGetResources(handler, resources);
}
······
}
核心方法就是TINKER_LOADER_ENTRY_CLASSNAME定义的Loader类中的tryload方法,所以加载的核心代码还得继续往下扒。
public class TinkerLoader extends AbstractTinkerLoader {
@Override // com.tencent.tinker.loader.AbstractTinkerLoader
public Intent tryLoad(TinkerApplication tinkerApplication) {
Guard guard;
ShareTinkerLog.d(TAG, "tryLoad test test", new Object[0]);
Intent intent = new Intent();
Guard[] guardArr = new Guard[1];
long jElapsedRealtime = SystemClock.elapsedRealtime();
tryLoadPatchFilesInternal(tinkerApplication, intent, guardArr);
ShareIntentUtil.setIntentPatchCostTime(intent, SystemClock.elapsedRealtime() - jElapsedRealtime);
if (ShareIntentUtil.getIntentReturnCode(intent) != 0 && (guard = guardArr[0]) != null) {
guard.close();
}
sProcessGuardRef = guardArr[0];
if (ShareTinkerInternals.isInMainProcess(tinkerApplication)) {
tryCleanObsoletePatches(tinkerApplication);
}
return intent;
}
private void tryLoadPatchFilesInternal(TinkerApplication app, Intent resultIntent) {
// 检查是否在 patch 进程(避免递归加载)
if (ShareTinkerInternals.isInPatchProcess(app)) {
ShareIntentUtil.setIntentReturnCode(resultIntent, ShareConstants.ERROR_LOAD_DISABLE);
return;
}
// ========== 1. 获取补丁目录 ==========
File patchDirectory = SharePatchFileUtil.getPatchDirectory(app);
if (patchDirectory == null || !patchDirectory.exists()) {
ShareIntentUtil.setIntentReturnCode(resultIntent, ShareConstants.ERROR_LOAD_PATCH_DIRECTORY_NOT_EXIST);
return;
}
// ========== 2. 读取 patch.info ==========
File patchInfoFile = SharePatchFileUtil.getPatchInfoFile(patchDirectory.getAbsolutePath());
File patchInfoLockFile = SharePatchFileUtil.getPatchInfoLockFile(patchDirectory.getAbsolutePath());
SharePatchInfo patchInfo = SharePatchInfo.readAndCheckPropertyWithLock(patchInfoFile, patchInfoLockFile);
if (patchInfo == null) {
ShareIntentUtil.setIntentReturnCode(resultIntent, ShareConstants.ERROR_LOAD_PATCH_INFO_CORRUPTED);
return;
}
String oldVersion = patchInfo.oldVersion; // 当前已加载的补丁版本
String newVersion = patchInfo.newVersion; // 新补丁版本
boolean isMainProcess = ShareTinkerInternals.isInMainProcess(app);
boolean versionChanged = !oldVersion.equals(newVersion);
// ========== 3. 版本决策 ==========
String versionToLoad;
if (versionChanged && isMainProcess) {
versionToLoad = newVersion; // 主进程加载新版本
} else {
versionToLoad = oldVersion; // 非主进程继续用旧版本
}
// ========== 4. 定位补丁文件 ==========
String patchVersionDir = patchDirectory.getAbsolutePath() + "/" +
SharePatchFileUtil.getPatchVersionDirectory(versionToLoad);
File patchVersionFile = new File(patchVersionDir, "patch-" + versionToLoad + ".apk");
if (!patchVersionFile.exists()) {
ShareIntentUtil.setIntentReturnCode(resultIntent, ShareConstants.ERROR_LOAD_PATCH_VERSION_NOT_EXIST);
return;
}
// ========== 5. 分发到具体 Loader ==========
// Dex 补丁校验与加载
if (ShareTinkerInternals.isTinkerEnabledForDex(tinkerFlags)) {
boolean dexCheck = TinkerDexLoader.checkComplete(patchVersionDir, securityCheck,
patchInfo.oatDir, resultIntent);
if (!dexCheck) {
ShareTinkerLog.w(TAG, "tryLoadPatchFiles: dex check fail");
return;
}
}
// So 补丁校验
if (ShareTinkerInternals.isTinkerEnabledForNativeLib(tinkerFlags)) {
}
// 资源补丁校验
if (ShareTinkerInternals.isTinkerEnabledForResource(tinkerFlags)) {
}
// arkHot校验
if (ShareTinkerInternals.isTinkerEnabledForArkHot(tinkerFlags)) {
}
// ========== 6. 安全模式检查 ==========
if (!checkSafeModeCount(app)) {
ShareTinkerLog.w(TAG, "tryLoadPatchFiles: safe mode count check fail");
if (isMainProcess) {
// 主进程:清除补丁并自杀
SharePatchInfo.rewritePatchInfoFileWithLock(patchInfoFile, new SharePatchInfo(), patchInfoLockFile);
ShareTinkerInternals.killProcessExceptMain(app);
}
ShareIntentUtil.setIntentReturnCode(resultIntent, ShareConstants.ERROR_LOAD_SAFE_MODE_COUNT_FAIL);
return;
}
// ========== 7. 最终加载:修改 ClassLoader ==========
//加载 Dex 补丁(修改 dexElements)
if (ShareTinkerInternals.isTinkerEnabledForDex(tinkerFlags)) {
// 核心代码,加载补丁代码
boolean loadDex = TinkerDexLoader.loadTinkerJars(app, patchVersionDir, patchInfo.oatDir,
resultIntent, isSystemOTA, patchInfo.fingerPrint);
if (!loadDex) {
ShareTinkerLog.w(TAG, "tryLoadPatchFiles: load dex fail");
return;
}
// 更新 patch.info 中的 oatDir
patchInfo.oatDir = TinkerDexLoader.getLastOatDir();
SharePatchInfo.rewritePatchInfoFileWithLock(patchInfoFile, patchInfo, patchInfoLockFile);
}
// 加载资源补丁(重建 AssetManager)
if (ShareTinkerInternals.isTinkerEnabledForResources(tinkerFlags)) {}
// 加载arkHot
if (ShareTinkerInternals.isTinkerEnabledForArkHot(tinkerFlags)) {}
// ========== 8. 清理过期补丁 ==========
if (isMainProcess) {
tryCleanObsoletePatches(app);
}
ShareIntentUtil.setIntentReturnCode(resultIntent, ShareConstants.ERROR_LOAD_OK);
ShareTinkerLog.i(TAG, "tryLoadPatchFiles: load end, ok!");
}
}
这个tryLoadPatchFilesInternal方法过长,可能jadx等反编译工具无法解析,这里可以参考tinker开源项目中的源码,前面一大长代码都是资源校验与版本控制,最核心的代码其实就这行TinkerDexLoader.loadTinkerJars,类似的还有TinkerArkHotLoader 、 TinkerResourceLoader等。
//TinkerDexLoader
/**
* Tinker Dex 补丁加载的核心方法(精简版)
* 只保留关键步骤,去掉日志和细节处理
*/
public static boolean loadTinkerJars(TinkerApplication tinkerApplication, String patchDir,
String oatDir, Intent intent, boolean isSystemOTA, boolean isInterpretMode) {
// ==================== 第一步:获取系统的 ClassLoader ====================
ClassLoader classLoader = TinkerDexLoader.class.getClassLoader();
// ==================== 第二步:收集所有补丁 Dex 文件 ====================
String dexPath = patchDir + "/dex/";
ArrayList<File> patchDexFiles = new ArrayList<>();
// 2.1 收集普通补丁 Dex
for (ShareDexDiffPatchInfo info : LOAD_DEX_LIST) {
// do something
patchDexFiles.add(dexFile);
}
// 2.2 收集 classN.dex(ART 环境需要)
if (isVmArt && !classNDexInfo.isEmpty()) {
// do something
patchDexFiles.add(classNDex);
}
// ==================== 第三步:确定 OAT 优化目录 ====================
File optimizeDir;
if (isSystemOTA) {
// 系统 OTA 后需要重新优化 Dex
try {
String instructionSet = ShareTinkerInternals.getCurrentInstructionSet();
deleteOutOfDateOATFile(patchDir);
optimizeDir = new File(patchDir + "/interpet");
// 并行优化所有 Dex,生成 OAT 文件
TinkerDexOptimizer.optimizeAll(tinkerApplication, patchDexFiles, optimizeDir, true, instructionSet, ...);
} catch (Throwable t) {
}
} else {
optimizeDir = new File(patchDir + "/" + oatDir);
}
// ==================== 第四步:将补丁 Dex 注入 ClassLoader ====================
try {
// 通过反射修改 ClassLoader 的 dexElements 数组
// 将补丁类与当前的类数组合并,并且保证补丁 Dex 插入到数组最前面,实现优先加载
SystemClassLoaderAdder.installDexes(tinkerApplication, classLoader, optimizeDir, patchDexFiles, isInterpretMode, ...);
return true;
} catch (Throwable t) {
}
}
// SystemClassLoaderAdder.installDexes 核心逻辑
public static void installDexes(Application app, ClassLoader loader, File optimizeDir,
List<File> dexFiles, boolean isInterpretMode, ...) {
// 1. 获取 PathClassLoader 的 pathList 字段
// PathClassLoader 内部有一个 DexPathList 类型的 pathList 字段
Object pathList = getPathList(loader);
// 2. 获取原有的 dexElements 数组
// DexPathList 内部有一个 Element[] 类型的 dexElements 数组
Object[] originalElements = getDexElements(pathList);
// 3. 将补丁 Dex 文件转换为 Element 对象数组
// 每个 Dex 文件对应一个 Element 对象
Object[] patchElements = makeDexElements(dexFiles, optimizeDir);
// 4. 关键:合并数组,补丁在前,原 Dex 在后
Object[] newElements = combineArray(patchElements, originalElements);
// 5. 将新数组设置回 pathList
setDexElements(pathList, newElements);
}
这个方法核心逻辑其实就是拿到 ClassLoader → 校验补丁 Dex → 准备优化目录 → 反射修改 dexElements 让补丁优先加载。但是这里其实只是告诉系统后续加载顺序,会优先加载补丁类,但是如果这个类已经被加载了,其实后续是不会再去加载的,所以tinker的热更新需要重启app才能完全生效。

- installDexes 只是修改了查找路径,没有加载任何补丁类
- 补丁类是在第一次被使用时,由系统 ClassLoader 按新的 dexElements 顺序加载的
- 如果一个类在 installDexes 之前已经被加载了,那么它不会被重新加载,补丁对它不生效(这是 Tinker 需要重启的原因)
| 时机 | 类加载状态 | 补丁效果 |
|---|---|---|
| 当前进程 | 很多类已经加载到内存 | 已加载的类无法被替换 |
| 下次冷启动 | 所有类重新加载,ClassLoader 已修改 | ✅ 补丁中的类被优先加载 |

相比robust的字节码插桩技术,tinker是通过差分合并dex文件,修改classLoader加载顺序保证热更新,优点是功能全面,支持代码、资源、So库的完整修复,可新增类、字段、方法,且支持与原类同名替换,运行时性能损耗影响更小。但是缺点也很明显,新增类会导致补丁包体积较大(补丁包较大,但是原始apk体积更占优),且需要冷启动触发重启类加载才能真正完全生效。
阿里热更新AndFix /Sophix
the same, pull下支付宝的apk
adb shell pm list packages | grep Alipay
adb shell pm path com.eg.android.AlipayGphone
adb pull /data/app/XXX.AlipayGphone-xxx/base.apk E:\apks\alipay\
通过查看Robust和Tinker源码,可以看到其核心思想分别是插桩拦截问题方法和修改类加载器中的class顺序拦截问题代码类,AndFix的思想则是直接替换native层的虚拟机中的方法。
AndFix开源项目
Sophix式对AndFix的技术升级与整合,本来想反编译支付宝和淘宝apk找源代码看的,结果都被混淆了,搜不到Sophix相关的类。没招了,只能找网上的开源项目和问问大模型学习了。不过支付宝还保留了AndFix的相关代码(在com.alipay.euler.andfix下,不过部分代码被混淆了,建议还是直接看开源源码)。
Sophix与Tinker或Robust的最大不同在于,它采用了一套融合方案:在大多数情况下,它能实现即时生效(热部署)(整合了AndFix),而当遇到无法即时修复的情况时,会自动降级为重启生效(冷部署)(在 App 下次启动时,通过修改 ClassLoader 的查找顺序,让系统优先加载补丁中的类,从而实现对原有 Bug 类的覆盖。和tinker思想基本一致)。
所以这里主要研究下AndFix是如何做到native层的虚拟机中的方法的,在ART虚拟机中,每个Java方法都对应一个底层的ArtMethod结构体(ART一般不被认为是一个标准的JVM实现)。
AndFix通过JNI在Native层获取新旧方法的ArtMethod指针,然后逐个字段地进行替换
// AndFixManager 找到对应的补丁文件
public synchronized void fix(String patchPath) {
fix(new File(patchPath), mContext.getClassLoader(), null);
}
// 对加载出的类遍历,判断是否需要进行替换
private void fixClass(Class<?> clazz, ClassLoader classLoader) {
Method[] methods = clazz.getDeclaredMethods();
MethodReplace methodReplace;
String clz;
String meth;
for (Method method : methods) {
methodReplace = method.getAnnotation(MethodReplace.class);
if (methodReplace == null)
continue;
clz = methodReplace.clazz();
meth = methodReplace.method();
if (!isEmpty(clz) && !isEmpty(meth)) {
replaceMethod(classLoader, clz, meth, method);
}
}
}
// Java层入口:AndFix.java
private static native void replaceMethod(Method src, Method dest);
// Native层:andfix.cpp
static void replaceMethod(JNIEnv* env, jclass clazz, jobject src, jobject dest) {
if (isArt) {
art_replaceMethod(env, src, dest); // ART环境
} else {
dalvik_replaceMethod(env, src, dest); // Dalvik环境
}
}
由于不同Android版本的ArtMethod结构不同,AndFix需要针对每个版本编写单独的替换函数
// art_method_replace.cpp
extern void art_replaceMethod(JNIEnv* env, jobject src, jobject dest) {
if (apilevel > 23) {
replace_7_0(env, src, dest); // Android 7.0
} else if (apilevel > 22) {
replace_6_0(env, src, dest); // Android 6.0
} else if (apilevel > 21) {
replace_5_1(env, src, dest); // Android 5.1
} else if (apilevel > 19) {
replace_5_0(env, src, dest); // Android 5.0
} else {
replace_4_4(env, src, dest); // Android 4.4
}
}
void replace_7_0(JNIEnv* env, jobject src, jobject dest) {
// 获取ArtMethod指针
art::mirror::ArtMethod* smeth = (art::mirror::ArtMethod*) env->FromReflectedMethod(src);
art::mirror::ArtMethod* dmeth = (art::mirror::ArtMethod*) env->FromReflectedMethod(dest);
// 逐个字段替换(需要知道每个Android版本的结构)
smeth->declaring_class_ = dmeth->declaring_class_;
smeth->access_flags_ = dmeth->access_flags_;
smeth->dex_code_item_offset_ = dmeth->dex_code_item_offset_;
smeth->dex_method_index_ = dmeth->dex_method_index_;
smeth->method_index_ = dmeth->method_index_;
smeth->hotness_count_ = dmeth->hotness_count_;
smeth->ptr_sized_fields_.dex_cache_resolved_methods_ =
dmeth->ptr_sized_fields_.dex_cache_resolved_methods_;
smeth->ptr_sized_fields_.dex_cache_resolved_types_ =
dmeth->ptr_sized_fields_.dex_cache_resolved_types_ ;
// 替换方法入口(最关键的一步)
smeth->ptr_sized_fields_.entry_point_from_jni_ =
dmeth->ptr_sized_fields_.entry_point_from_jni_;
smeth->ptr_sized_fields_.entry_point_from_quick_compiled_code_ =
dmeth->ptr_sized_fields_.entry_point_from_quick_compiled_code_;
}
从上面的代码可以看出,AndFix核心思想就是替换方法结构体的每一个参数, 但是每一个安卓版本得ART中的方法实现都不一样,这就需要为每个 Android 版本编写不同的替换逻辑,而且不同厂商的手机可能会自己修改ROM,所以很容易引发兼容性问题。
优化:Sophix取消了参数一个一个替换的逻辑,采用整体memcpy替换 + 动态计算结构体大小的方式。
由于不同厂商、不同版本的ArtMethod结构体大小不同,Sophix采用了一种巧妙的运行时动态计算方式:
// 1. 在Java层构造一个只有两个空方法的类(Sophix源码)
public class NativeStructsModel {
final public static void f1() {}
final public static void f2() {}
}
// 2. 在Native层获取这两个方法的地址,计算差值
JNIEXPORT jlong JNICALL Java_com_taobao_sophix_NativeBridge_getArtMethodSize(JNIEnv* env, jclass clazz) {
jclass modelClass = env->FindClass("com/taobao/sophix/NativeStructsModel");
// 获取两个空方法的jmethodID
jmethodID mid1 = env->GetStaticMethodID(modelClass, "f1", "()V");
jmethodID mid2 = env->GetStaticMethodID(modelClass, "f2", "()V");
// 将jmethodID转换为ArtMethod指针
ArtMethod* method1 = (ArtMethod*) mid1;
ArtMethod* method2 = (ArtMethod*) mid2;
// 关键:相邻方法的地址差值 = ArtMethod的大小
size_t methodSize = (size_t)method2 - (size_t)method1;
return (jlong) methodSize;
}
// Sophix的Native层代码
JNIEXPORT void JNICALL Java_com_taobao_sophix_NativeBridge_replaceMethod(JNIEnv* env, jclass clazz, jobject srcMethod, jobject destMethod, jlong artMethodSize) {
// 1. 获取ArtMethod指针
ArtMethod* src = (ArtMethod*) env->FromReflectedMethod(srcMethod);
ArtMethod* dest = (ArtMethod*) env->FromReflectedMethod(destMethod);
// 2. 获取ArtMethod的实际大小(运行时传入,不依赖编译时定义)
size_t methodSize = (size_t) artMethodSize;
// 3. 保存旧方法的访问标志(用于后续处理)
uint32_t oldAccessFlags = src->access_flags_;
// 4. 整体内存替换
memcpy(src, dest, methodSize);
// 5. 恢复关键字段(保持类的结构完整性)
src->declaring_class_ = dest->declaring_class_;
src->access_flags_ = oldAccessFlags;
// 6. 处理特殊方法(如构造方法、静态初始化块)
if (isConstructor(src) || isClassInitializer(src)) {
// 特殊处理逻辑...
}
}
通过直接修改虚拟机中的C++方法,达到实时更新的目的,只能说操作很6。
总结
上面浅显地介绍了美团robust、微信tinker、阿里的AndFix和Sophix实现原理,我们总结一下他们的优缺点。
1、 核心原理对比
| 对比维度 | Robust (美团) | Tinker (微信) | Sophix (阿里) |
|---|---|---|---|
| 底层技术 | 编译期字节码插桩 | Dex差分 + ClassLoader替换 | C++底层替换 + 类加载混合 |
| 核心机制 | 每个方法入口插入 if (changeQuickRedirect != null) 判断 |
通过反射修改 dexElements 数组,将补丁 Dex 前置 |
小改动 Native 层替换 ArtMethod;大改动冷启动类加载 |
| 生效方式 | 即时生效 | 冷启动生效(需重启 App) | 智能选择:小改动即时生效,大改动冷启动生效 |
| 修复粒度 | 方法级 | 类/DEX 级 | 方法级 + 类级 |
2、 优缺点对比
| 方案 | 优点 | 缺点 |
|---|---|---|
| Robust (美团) | ✅ 即时生效:无需重启,用户无感知 ✅ 补丁包极小:只包含变更的方法代码 ✅ 兼容性极高:纯 Java 实现,不受系统版本影响 ✅ 成功率极高:官方宣称 99.99% ✅ 接入简单:Gradle 插件自动插桩 |
❌ 增加 APK 体积:每个方法插入约 17 字节代码 ❌ 功能有限:不支持资源和 So 库修复 ❌ 运行时性能损耗:每个方法多一次 if 判断❌ 不支持类结构变更:无法新增类/字段/方法 ❌ 影响 ProGuard 内联优化 |
| Tinker (微信) | ✅ 功能最全面:支持代码、资源、So 库修复 ✅ 支持类结构变更:可新增类、字段、方法 ✅ 兼容性高:基于官方 Dex 方案,全版本适配 ✅ 开发透明:无需修改业务代码 ✅ 经过大规模验证:微信数亿设备验证 |
❌ 需要重启生效:用户体验较差 ❌ 补丁包较大:类级替换,差量包相对较大 ❌ 合成内存开销大:Dex 合并有 OOM 风险 ❌ 不支持 AndroidManifest 修改 ❌ 接入成本较高:需要配置 Gradle 插件 |
| Sophix (阿里) | ✅ 智能生效:小改动即时生效,大改动冷启动 ✅ 功能全面:支持代码、资源、So 库修复 ✅ 兼容性极高: memcpy 整体替换,无视 ROM 差异✅ 补丁包小:差量算法优化 ✅ 支持类结构变更:可新增类/字段/方法 |
❌ 商业产品:核心功能需付费(阿里云 EMAS) ❌ 接入依赖阿里云:需要配置云端服务 ❌ 闭源:核心 Native 代码未开源 ❌ 社区资料较少:主要依赖官方文档 ❌ 即时生效场景有限制:复杂改动需重启 |