一、微信
tinker热修复 使用 framework java
核心:在加载bug的dex文件前
加载补丁dex文件
现有apk里有dex1、dex2两个文件,
假设dex1中某个类ClassA某个方法中有bug需紧急热修复,
服务端将修复好的类打包成dex3文件,
那么客户端需要通过服务器下载dex3文件到手机并在加载dex1之前加载dex3
原理:加载了dex3中已修复的类ClassA后就不会再
加载dex1中的带有bug的类ClassA
总结:类替换
二、阿里云
github传送门
核心:通过jni方法替换出现bug的java代码中的method,方法的替换
修复步骤:
- 服务端生成补丁包dex文件(用注解标注要修改的类及方法)让用户下载
- 客户端通过网络下载修复好的dex包
- 客户端加载修复好的dex包,通过反射注解定位到出bug的类及方法
public void loadDex(File dexFilePath) {
File optFile = new File(context.getCacheDir(), dexFilePath.getName());
if (optFile.exists()) {
optFile.delete();
}
try {
// 加载dex,若dex在SD卡,则需要先申请SD的读写权限
DexFile dexFile = DexFile.loadDex(dexFilePath.getAbsolutePath(), optFile.getAbsolutePath(), Context.MODE_PRIVATE);
Enumeration<String> enumeration = dexFile.entries();
while (enumeration.hasMoreElements()) {
String className = enumeration.nextElement();
Class realClazz = dexFile.loadClass(className, context.getClassLoader());
Log.i(TAG, "loadDex: 找到类=" + className);
fix(realClazz);
}
} catch (IOException e) {
e.printStackTrace();
/* java.io.IOException: No original dex files found for dex location /storage/emulated/0/_ae/fix.dex
* 是缺少授予WRITE_EXTERNAL_STORAGE权限
* */
}
}
private void fix(Class realClazz) {
Method[] declaredMethods = realClazz.getDeclaredMethods();
for (Method method : declaredMethods) {
Replace replace = method.getAnnotation(Replace.class);
if (replace == null) {
continue;
}
String wrongClazzName = replace.clazz();
String wrongMethod1Name = replace.method();
try {
// TODO: 2019/6/29 混淆需要一些其它处理
Class<?> wrongClazz = Class.forName(wrongClazzName);
// 修复带参方法时,注意参数问题
Method wrongMethod = wrongClazz.getMethod(wrongMethod1Name, method.getParameterTypes());
replace(wrongMethod, method);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
}
- 核心:通过JNI替换Java代码中出现bug的method
参考 http://androidxref.com/9.0.0_r3/xref/art/runtime/art_method.h
写一个自定义的头文件,名字随意,比如art.h
,代码如下
//
// Created by yuan on 2019/6/29.
//
#include <cstdint>
#ifndef HOTFIX_ART_H
#define HOTFIX_ART_H
namespace art {
class ArtMethod {
public:
uint32_t declaring_class_;
uint32_t access_flags_;
uint32_t dex_code_item_offset_;
uint32_t dex_method_index_;
uint16_t method_index_;
uint16_t hotness_count_;
struct PtrSizedFields {
ArtMethod **dex_cache_resolved_methods_;
uint32_t *dex_cache_resolved_types_;
void *entry_point_from_jni_;
void *entry_point_from_quick_compiled_code_;
} ptr_sized_fields_;
};
} // namespace art
#endif //HOTFIX_ART_H
自定义头文件只是声明,使如下代码通过编译,代码真正执行仍是依靠手机系统中的art_method.h
#include <jni.h>
#include <string>
#include "art.h"
extern "C" JNIEXPORT void JNICALL
Java_com_yuan_hotfix_andfix_DxManager_replaceJNI(JNIEnv *env, jobject instance, jobject wrongMethod,
jobject rightMethod) {
art::ArtMethod *artReplaceMethod = (art::ArtMethod *) env->FromReflectedMethod(
wrongMethod);
art::ArtMethod *artMethod = (art::ArtMethod *) env->FromReflectedMethod(
rightMethod);
// 自定义的头文件只是声明,使如下代码通过编译,代码真正执行仍是依靠手机系统中的art_method.h
artReplaceMethod->access_flags_ = artMethod->access_flags_;
artReplaceMethod->declaring_class_ = artMethod->declaring_class_;
artReplaceMethod->dex_code_item_offset_ = artMethod->dex_code_item_offset_;
artReplaceMethod->dex_method_index_ = artMethod->dex_method_index_;
artReplaceMethod->hotness_count_ = artMethod->hotness_count_;
artReplaceMethod->method_index_ = artMethod->method_index_;
artReplaceMethod->ptr_sized_fields_.dex_cache_resolved_methods_ = artMethod->ptr_sized_fields_.dex_cache_resolved_methods_;
artReplaceMethod->ptr_sized_fields_.dex_cache_resolved_types_ = artMethod->ptr_sized_fields_.dex_cache_resolved_types_;
artReplaceMethod->ptr_sized_fields_.entry_point_from_jni_ = artMethod->ptr_sized_fields_.entry_point_from_jni_;
artReplaceMethod->ptr_sized_fields_.entry_point_from_quick_compiled_code_ = artMethod->ptr_sized_fields_.entry_point_from_quick_compiled_code_;
printf("com.yuan.hotfix 555");
}
核心原理的示意图
三、apk打包流程:
java -> class -> dex -> apk
1. java -> class
Android Studio生成的class文件
\build\intermediates\javac\debug\compileDebugJavaWithJavac\classes\com\yuan\hotfix\andfix\ok\Calculator.class
或者
\build\intermediates\classes\debug\com\yuan\testandfix\ok\Calculator.class
2. class -> dex
借助工具 Sdk\build-tools\28.0.3\dx.bat
- 比如在桌面建一个文件夹aa,拷贝
\build\intermediates\javac\debug\compileDebugJavaWithJavac\classes\
下的com文件夹到aa下,删除不需要打包的class文件,比如只保留需要打包的com\yuan\hotfix\andfix\ok\Calculator.class文件,
因为Calculator.class里有com.yuan.hotfix.andfix.ok
,所以需要com\yuan\hotfix\andfix\ok\
一系列的文件夹- cd到如下目录
C:\Users\ran\AppData\Local\Android\Sdk\build-tools\28.0.3>
或者把dx.bat配置到环境变量中- 执行
dx.bat --dex --output C:\Users\ran\Desktop\aa\fix.dex C:\Users\ran\Desktop\aa
就会把aa文件夹下的所有class打包到aa/fix.dex中
C++实现java方法替换,热修复原理剖析及其实现
andfix热修复.mp4
Anroid分析Andfix原理手写实现
https://blog.csdn.net/qq_23213991/article/details/83342617
ps:
DexFile的API
.so与.dex的区别
网友评论