AndFix,全称是Android hot-fix。是阿里开源的一个Android热补丁框架,允许APP在不重新发布版本的情况下修复线上的bug。支持Android 2.3 到 7.0。
一、动态加载
热修复、热更新、插件化都是利用动态加载的原理(相关知识:类加载机制、虚拟机)
二、热修复
Andfix(最轻量级的热修复,只能改方法)Android热修复的先驱,通过动态加载dex文件,在native层进行方法替换实现热修复,局限性:兼容性问题,每个Android版本都需要维护一套Andfix的实现代码,维护麻烦,现该开源库已停止维护了。优点:即时生效。
Tink dex替换,是在java层实现,不足:冷启动后生效
三、源码追踪
要通过方法替换实现热修复,那么就需要去了解一下底层的类加载机制,特别是方法加载这一块的具体实现流程。
大概流程就是先加载类,然后再将类中的method和field加载到class中,具体如下:
1、env->FindClass jni函数中找到类的函数(方法在类里面,根据类加载找到方法加载的途径)
2、ClassLinker::FindClass 是env->FindClass的主要实现的函数。
3、DefineClass 是ClassLinker::FindClass 中返回的类的类型。
4、LoadClass 在DefineClass 中调用,此函数是加载类的主要函数。
5、LoadClassMembers 在LoadClass中调用,此函数是加载方法的函数。
6、LinkCode 在LoadClassMembers中调用,此函数是加载字节码的函数。
7、LinkMethod 在LinkCode中调用,此函数设置方法的执行入口(两种:SetEntryPointFromPortableCompiledCode 和 SetEntryPointFromQuickCompiledCode)。
8、NeedsInterpreter 在LinkCode中调用,此函数设置是否需要解释器。
9、UnregisterNative 在LinkCode中调用,此函数中设置了访问权限和入口。
10、UpdateMethodsCode 在LinkCode中调用,此函数刷新方法的字节码。
11、UpdateEntrypoints 在UpdateMethodsCode中调用,此函数更新方法入口。
四、源码分析:
这里只列出关键代码片段,想直接看源码的话可以点每个方法的链接进去网页看
/art/runtime/mirror/art_method.h
art_method 方法的结构体
592 struct PACKED(4) PtrSizedFields {
593 // Method dispatch from the interpreter invokes this pointer which may cause a bridge into
594 // compiled code.解释器模式(JIT)方法入口指针
595 void* entry_point_from_interpreter_;
596
597 // Pointer to JNI function registered to this method, or a function to resolve the JNI function.jni方法入口指针
598 void* entry_point_from_jni_;
599
600 // Method dispatch from quick compiled code invokes this pointer which may cause bridging into
601 // portable compiled code or the interpreter.快速模式,预编译模式AOT(ahead of time)AOT:Ahead Of Time,指在运行前编译,比如普通的静态编译 JIT:Just In Time,指在运行时编译,边运行边编译,比如java虚拟机在运行时就用到JIT技术
602 void* entry_point_from_quick_compiled_code_;
FindClass (env->FindClass) 寻找类
589 static jclass FindClass(JNIEnv* env, const char* name) {
590 CHECK_NON_NULL_ARGUMENT(name);
591 Runtime* runtime = Runtime::Current();
592 ClassLinker* class_linker = runtime->GetClassLinker();//类的链接器
593 std::string descriptor(NormalizeJniClassDescriptor(name));
594 ScopedObjectAccess soa(env);
595 mirror::Class* c = nullptr;
596 if (runtime->IsStarted()) {//是否已经初始化完成
597 StackHandleScope<1> hs(soa.Self());
598 Handle<mirror::ClassLoader> class_loader(hs.NewHandle(GetClassLoader(soa)));
599 c = class_linker->FindClass(soa.Self(), descriptor.c_str(), class_loader);
//调用classLinker的findClass,(参数1:来自env,参数2:来自类名,参数3:类加载器)
600 } else {
601 c = class_linker->FindSystemClass(soa.Self(), descriptor.c_str());
602 }
603 return soa.AddLocalReference<jclass>(c);
604 }
ClassLinker::FindClass FindClass的具体实现,根据返回的DefineClass继续追踪代码
//参数self:线程,参数descriptor:类名的标识,参数class_loader:类加载器
2117 mirror::Class* ClassLinker::FindClass(Thread* self, const char* descriptor,
2118 Handle<mirror::ClassLoader> class_loader) {
//此处省略code
//双亲委托机制,类加载过就不会再加载
2128 // Find the class in the loaded classes table.
2129 mirror::Class* klass = LookupClass(descriptor, hash, class_loader.Get());
2130 if (klass != nullptr) {
2131 return EnsureResolved(self, descriptor, klass);
2132 }
2133 // Class is not yet loaded.
2134 if (descriptor[0] == '[') {
2135 return CreateArrayClass(self, descriptor, hash, class_loader);
2136 } else if (class_loader.Get() == nullptr) {//判断类加载器是否为空,这里是类加载器为空的处理
//从系统启动类里找,若找到,返回一个DefineClass
2137 // The boot class loader, search the boot class path.
2138 ClassPathEntry pair = FindInClassPath(descriptor, hash, boot_class_path_);
2139 if (pair.second != nullptr) {
2140 return DefineClass(self, descriptor, hash, NullHandle<mirror::ClassLoader>(), *pair.first,
2141 *pair.second);
2142 } else {//报错,ClassNotFoundException
2146 mirror::Throwable* pre_allocated = Runtime::Current()->GetPreAllocatedNoClassDefFoundError();
2147 self->SetException(ThrowLocation(), pre_allocated);
2148 return nullptr;
2149 }
2150 } else if (Runtime::Current()->UseCompileTimeClassPath()) {//这里是类加载器不为空的处理
2151 //此处省略code
2216}
ClassLinker::DefineClass 对kclass进行赋值,其中LoadClass方法实现了类加载的主要功能。
2218mirror::Class* ClassLinker::DefineClass(Thread* self, const char* descriptor, size_t hash,
2219 Handle<mirror::ClassLoader> class_loader,
2220 const DexFile& dex_file,
2221 const DexFile::ClassDef& dex_class_def) {
//此处省略code
2225 // Load the class from the dex file.
//类未加载就对kclass进行赋值
2226 if (UNLIKELY(!init_done_)) {
2227 // finish up init of hand crafted class_roots_
2228 if (strcmp(descriptor, "Ljava/lang/Object;") == 0) {
2229 klass.Assign(GetClassRoot(kJavaLangObject));
2230 } else if (strcmp(descriptor, "Ljava/lang/Class;") == 0) {
2231 //此处省略code
2243 }
2244
2245 if (klass.Get() == nullptr) {
//申请空间
2250 klass.Assign(AllocClass(self, SizeOfClassWithoutEmbeddedTables(dex_file, dex_class_def)));
//此处省略code
//加载类
2257 LoadClass(dex_file, dex_class_def, klass, class_loader.Get());
//此处省略code
2320}
ClassLinker::LoadClass 把dex文件的信息初始化后放到class中,其中LoadClassMembers方法将类的成员加载到类中
//DexFile:dex文件对象,DexFile::ClassDef:要加载的类在dex中的描述信息
2727 void ClassLinker::LoadClass(const DexFile& dex_file,
2728 const DexFile::ClassDef& dex_class_def,
2729 Handle<mirror::Class> klass,
2730 mirror::ClassLoader* class_loader) {
//初始化
2734 const char* descriptor = dex_file.GetClassDescriptor(dex_class_def);
2735 CHECK(descriptor != nullptr);
2736
2737 klass->SetClass(GetClassRoot(kJavaLangClass));
//此处省略kclass的一些赋值操作
2747 //拿到类的索引放入class中
2748 klass->SetDexClassDefIndex(dex_file.GetIndexForClassDef(dex_class_def));
//此处省略kclass的一些赋值操作
2757 OatFile::OatClass oat_class;
2758 if (Runtime::Current()->IsStarted()
2759 && !Runtime::Current()->UseCompileTimeClassPath()
2760 && FindOatClass(dex_file, klass->GetDexClassDefIndex(), &oat_class)) {
2761 LoadClassMembers(dex_file, class_data, klass, class_loader, &oat_class);//加载class的成员
2762 } else {
2763 LoadClassMembers(dex_file, class_data, klass, class_loader, nullptr);
2764 }
2765}
ClassLinker::LoadClassMembers 加载类的静态、实例成员变量和方法到类中,最后执行LinkCode方法,链接字节码
2767 void ClassLinker::LoadClassMembers(const DexFile& dex_file,
2768 const byte* class_data,
2769 Handle<mirror::Class> klass,
2770 mirror::ClassLoader* class_loader,
2771 const OatFile::OatClass* oat_class) {
2772 // Load fields.
2773 ClassDataItemIterator it(dex_file, class_data);
2774 Thread* self = Thread::Current();
//静态成员变量
2775 if (it.NumStaticFields() != 0) {
//ArtField
2776 mirror::ObjectArray<mirror::ArtField>* statics = AllocArtFieldArray(self, it.NumStaticFields());
2777 if (UNLIKELY(statics == nullptr)) {
2778 CHECK(self->IsExceptionPending()); // OOME.
2779 return;
2780 }
//添加到kclass
2781 klass->SetSFields(statics);
2782 }
//实例成员变量
2783 if (it.NumInstanceFields() != 0) {
2784 mirror::ObjectArray<mirror::ArtField>* fields =
2785 AllocArtFieldArray(self, it.NumInstanceFields());
2786 if (UNLIKELY(fields == nullptr)) {
2787 CHECK(self->IsExceptionPending()); // OOME.
2788 return;
2789 }
2790 klass->SetIFields(fields);
2791 }
//此处省略kclass 的 filed 赋值操作
2813 // Load methods.
2814 if (it.NumDirectMethods() != 0) {
//ArtMethod
2816 mirror::ObjectArray<mirror::ArtMethod>* directs =
2817 AllocArtMethodArray(self, it.NumDirectMethods());
//此处省略kclass的一些method赋值操作
//链接code字节码
2845 LinkCode(method, oat_class, dex_file, it.GetMemberIndex(), class_def_method_index);
//此处省略code
2870}
LinkCode 根据之前存入kclass的索引,找到方法,然后设置方法执行入口()
2627 void ClassLinker::LinkCode(Handle<mirror::ArtMethod> method, const OatFile::OatClass* oat_class,
2628 const DexFile& dex_file, uint32_t dex_method_index,
2629 uint32_t method_index) {
//此处省略code
//GetOatMethod 通过方法索引找到方法
2642 const OatFile::OatMethod oat_method = oat_class->GetOatMethod(method_index);
//LinkMethod
2643 oat_method.LinkMethod(method.Get());
2644 }
2645
2646 // Install entry point from interpreter.
//NeedsInterpreter 是否需要解释器(native方法和代理方法没有dex字节码,不需要解释器执行)
//设置方法执行入口,方式与LinkMethod中设置的方式相同EntryPointFromQuickCompiledCode 和 EntryPointFromPortableCompiledCode
2647 bool enter_interpreter = NeedsInterpreter(method.Get(),
2648 method->GetEntryPointFromQuickCompiledCode(),
2649#if defined(ART_USE_PORTABLE_COMPILER)
2650 method->GetEntryPointFromPortableCompiledCode());
//此处省略code
//UnregisterNative 函数中设置了访问权限和入口
2705 method->UnregisterNative(Thread::Current());
//此处省略code
//将结构体数据赋值,并更新函数执行的入口 UpdateEntryPoints
2717 runtime->GetInstrumentation()->UpdateMethodsCode(method.Get(),
2718 method->GetEntryPointFromQuickCompiledCode(),
2719#if defined(ART_USE_PORTABLE_COMPILER)
2720 method->GetEntryPointFromPortableCompiledCode(),
2721#else
2722 nullptr,
2723#endif
2724 have_portable_code);
2725}
**OatFile::OatMethod::LinkMethod ** 设置两种方法执行入口
595void OatFile::OatMethod::LinkMethod(mirror::ArtMethod* method) const {
596 CHECK(method != NULL);
597#if defined(ART_USE_PORTABLE_COMPILER)
//设置两种方法执行入口
598 method->SetEntryPointFromPortableCompiledCode(GetPortableCode());
599#endif
600 method->SetEntryPointFromQuickCompiledCode(GetQuickCode());
601}
/art/runtime/mirror/art_method.cc
ArtMethod::UnregisterNative 注册方法
353 void ArtMethod::RegisterNative(Thread* self, const void* native_method, bool is_fast) {
//此处省略code
358 if (is_fast) {
//设置访问权限
359 SetAccessFlags(GetAccessFlags() | kAccFastNative);
360 }
//设置方法的入口
361 SetEntryPointFromJni(native_method);
362}
363
//UnregisterNative 函数内部调用了 RegisterNative
364void ArtMethod::UnregisterNative(Thread* self) {
365 CHECK(IsNative() && !IsFastNative()) << PrettyMethod(this);
366 // restore stub to lookup native pointer via dlsym
367 RegisterNative(self, GetJniDlsymLookupStub(), false);
368}
/art/runtime/instrumentation.cc
Instrumentation::UpdateMethodsCode 刷新方法的字节码
679void Instrumentation::UpdateMethodsCode(mirror::ArtMethod* method, const void* quick_code,
680 const void* portable_code, bool have_portable_code) {
//此处省略code
//更新方法入口
724 UpdateEntrypoints(method, new_quick_code, new_portable_code, new_have_portable_code);
725}
五、虚拟机的启动入口
虚拟机的入口函数:
/frameworks/base/core/jni/AndroidRuntime.cpp
AndroidRuntime::start
Android执行的第一个方法:
/frameworks/base/core/java/com/android/internal/os/ZygoteInit.java
public static void main(String argv[])
java虚拟机加载的第一个类是string,但是Android的入口方法在 ZygoteInit.java 类中
底层调用Android入口方法流程:
1、找类 ZygoteInit.java
2、找方法 public static void main(String argv[])
3、调用方法 public static void main(String argv[])
六、art_method结构体
art_method结构体用来存储java方法相关信息,所以我们只需要修改art_method结构体中相关信息和java方法入口指针即可实现方法的替换,即Andfix热修复。这是Andfix热修复的核心。
七、兼容性问题
兼容性问题原因:原理是修改art_method结构体,每个Android版本不一样,art_method都可能不一样,所以每个版本都要做个版本维护,兼容性差。
来源:https://blog.csdn.net/qq_35592743/article/details/100538089