在上一篇Andfix文章中(一)Andfix热修复原理中提到了,Andfix需要根据Android版本来维护,所以要适配不同的Android版本,这里根据Android5.1版本为例,简单介绍一下Andfix的手写实现。
一、生成修复后java类的dex文件
假如类A中的方法B有bug,新建一个A_fixed类,并添加一个B方法,这里的B方法是修复后的,然后重新编译,生成dex文件,在app/build/intermediates/javac/debug/complieDebugJavaWithJavac/classes/包名路径下有全部类的class,将完整包名文件夹都复制出来,然后文件夹中只留下修正后的类的class文件
用dx命令生成dex文件(需要配置环境变量,具体方法:
)
然后会在配置的路径下生成一个dex文件(将这个文件放到服务器)
二、创建一个DexManager管理类管理dex的替换
回到项目代码:
新建一个DexManager管理类管理dex的替换
//加载dex文件,拿到修复好的class文件
DexFile dexFile = DexFile.loadDex(file.getAbsolutePath(),new File(mContext.getCacheDir(),"opt").getAbsolutePath(),Context.MODE_PRIVATE);
Enumeration<String> entries = dexFile.entries();
while (entries.hasMoreElements()) {
String className = entries.nextElement();
Class fixedClass = dexFile.loadClass(className, mContext.getClassLoader());
if (fixedClass != null) {
fixClass(fixedClass);
}
}
要拿到修正的方法,就要在修正后的方法中添加自定义注解,告诉程序这个方法对应要替换的是哪个类的那个方法,自定义注解如下:
@MethodReplace(className = "包名.A",methodName = "B")
fixClass(Class fixClazz) 找到方法并替换:
Method[] methods = fixClazz.getDeclaredMethods();
MethodReplace methodReplace;
String className;
String methodName;
Class<?> bugClass;
Method bugMethod;
for (Method fixMethod : methods) {
methodReplace = fixMethod.getAnnotation(MethodReplace.class);
if (methodReplace == null) {
continue;
}
Log.e(TAG, "找到修复好的方法: " + fixMethod.getDeclaringClass() + "@" + fixMethod.getName());
className = methodReplace.className();
methodName = methodReplace.methodName();
if (!TextUtils.isEmpty(className) && !TextUtils.isEmpty(methodName)) {
try {
bugClass = Class.forName(className);
bugMethod =
bugClass.getDeclaredMethod(methodName, fixMethod.getParameterTypes());
replace(bugMethod, fixMethod);
Log.e(TAG, "修复完成!");
} catch (Exception e) {
e.printStackTrace();
}
}else {
Log.e(TAG, "/修复好的方法未设置自定义注解属性");
}
}
其中的replace是native方法:
private native void replace(Method bugMethod, Method fixMethod);
native层代码,利用ArtMethod结构体,实现方法替换:
extern "C"
JNIEXPORT void JNICALL
Java_com_netease_andfix_DexManager_replace(JNIEnv *env, jobject instance, jobject bugMethod,
jobject fixMethod) {
env->FindClass()
//ba获得指向被替换的目标方法的指针(有bug的方法的指针)
art::mirror::ArtMethod *bugArtMethod = reinterpret_cast<art::mirror::ArtMethod *>(env->FromReflectedMethod(
bugMethod));
//获得指向新的方法的指针(被修复了的方法的指针)
art::mirror::ArtMethod *fixArtMethod = reinterpret_cast<art::mirror::ArtMethod *>(env->FromReflectedMethod(
fixMethod));
reinterpret_cast<art::mirror::Class *>(fixArtMethod->declaring_class_)->class_loader_ =
reinterpret_cast<art::mirror::Class *>(bugArtMethod->declaring_class_)->class_loader_; //for plugin classloader
reinterpret_cast<art::mirror::Class *>(fixArtMethod->declaring_class_)->clinit_thread_id_ =
reinterpret_cast<art::mirror::Class *>(bugArtMethod->declaring_class_)->clinit_thread_id_;
reinterpret_cast<art::mirror::Class *>(fixArtMethod->declaring_class_)->status_ =
reinterpret_cast<art::mirror::Class *>(bugArtMethod->declaring_class_)->status_ - 1;
reinterpret_cast<art::mirror::Class *>(fixArtMethod->declaring_class_)->super_class_ = 0;
//把就函数的成员变量替换为新函数的
bugArtMethod->declaring_class_ = fixArtMethod->declaring_class_;
bugArtMethod->dex_cache_resolved_methods_ = fixArtMethod->dex_cache_resolved_methods_;
bugArtMethod->access_flags_ = fixArtMethod->access_flags_;
bugArtMethod->dex_cache_resolved_types_ = fixArtMethod->dex_cache_resolved_types_;
bugArtMethod->dex_code_item_offset_ = fixArtMethod->dex_code_item_offset_;
bugArtMethod->method_index_ = fixArtMethod->method_index_;
bugArtMethod->dex_method_index_ = fixArtMethod->dex_method_index_;
bugArtMethod->ptr_sized_fields_.entry_point_from_interpreter_ = fixArtMethod->ptr_sized_fields_.entry_point_from_interpreter_;
bugArtMethod->ptr_sized_fields_.entry_point_from_jni_ = fixArtMethod->ptr_sized_fields_.entry_point_from_jni_;
bugArtMethod->ptr_sized_fields_.entry_point_from_quick_compiled_code_ = fixArtMethod->ptr_sized_fields_.entry_point_from_quick_compiled_code_;
__android_log_print(ANDROID_LOG_ERROR, "AndFix","replace_5_1: %d , %d",
static_cast<const char *>(bugArtMethod->ptr_sized_fields_.entry_point_from_quick_compiled_code_),
fixArtMethod->ptr_sized_fields_.entry_point_from_quick_compiled_code_);
}