美文网首页
Andfix&Tinker 热修复方案原理

Andfix&Tinker 热修复方案原理

作者: carlwu_186 | 来源:发表于2022-06-12 09:06 被阅读0次

    Andfix

    andfix从native入手修改ArtMethod的字节码地址实现错误方法块的修复。修复的粒度是方法块字节码引用。

    Java的内存分布

    内存分布
    方法区
    JVM读取class文件,提取class的类型信息,并将这些信息存储到方法区,同时该类型的类静态变量也会放到方法区,还有方法表,每个类都会有个方法表。
    堆区
    主要存放的是对象,Java程序在运行时创建的所有类型的对象和数组都储存在堆中。
    JVM会根据new的指令在堆中开辟一个确定类型的对象内存空间,但是堆中开辟的对象空间并没有任何人工指令可以回收,而是通过JVM的垃圾回收机制进行回收。
    栈区
    每启动一个线程,JVM都会为它创建一个JAVA栈,用于存放方法中的局部变量、操作数等。
    当线程调用某个方法时,JVM会根据方法区中该方法的字节码组建一个栈帧,并将该栈帧压入到Java栈中,方法执行完毕后,JVM将该栈帧弹出,并释放掉。

    我们写的Java文件被编译成class文件到最终加载到内存,所属的内存区域是方法区,所以要做的就是,将差分包中没有bug的方法替换掉已经被加载到内存方法区中有bug方法。

    虚拟机运行机制

    虚拟机加载执行ActivityThread
    1. 声明一个ApplicationThread类型的成员变量
    //这一步不会将Application.class字节码加载到内存的,会在方法区生成一个int类型的符号表量
    private Application application
    

    对象只有在主动引用的情况下才会加载到内存,常见的主动引用方式有:new、反射创建、JNI的findCalss()、序列化、调用类的静态成员变量(final除外)和静态方法、初始化一个类如果其父类没有初始化,会先初始化父类。

    1. 通过反射的方式创建application对象,这一步会将application的字节码文件加载到内存,创建的对象存储在堆区。


      APP启动流程

      当通过Application对象调用onCreate()方法时,堆区的application对象指向int符号变量,int符号变量指向方法表,执行onCreate()方法,将onCreate组建成栈帧,压入Java栈,执行完毕后弹出并释放。

    方法表

    方法表可以理解为是一个数组,数组中存放的是ArtMethod结构体(Android5.0之前为Method)。ArtMethod中存放的方法入口、字节码地址等信息。

    扩展JVM DVM ART

    • JVM基于栈,意味着需要去栈中读写数据,所需要的指令会更多,这样会导致速度变慢,对于性能有限的移动设备显然不合适。
    • DVM是基于寄存器的,它没有基于栈的虚拟机在复制数据时而使用大量的出入栈指令,同事指令更紧凑、简洁。但是由于显式的制定了操作数,所以基于寄存器的指令会比基于栈的指令要大。
    • JVM中,java类被编译成一个或多个.class文件,并打包成.jar文件,而后JVM会通过相应的.class文件和.jar文件获取相应的字节码。
      而DVM会用dx工具把所有的class文件打包成一个.dex文件,然后DVM会从该.dex文件中读取指令和数据。这个.dex文件将所有的.class文件里面所包含的信息全部整合到了一起,这样加载就加快了速度。
    • DVM经过优化,允许在有限的内存中同时运行多个进程。在Android中每一个应用都运行在一个DVM实例中,每一个DVM实例都运行在一个独立的进程空间,独立的进程可以防止虚拟机崩溃时所有程序都被关闭。
    • Zygote是一个DVM进程,同时也用来创建和初始化其他DVM进程。每当系统需要一个应用程序进程的时候,Zygote就会fork自身,快速地创建和初始化一个DVM实例,用于程序运行。
    • DVM拥有预加载-共享机制,不同应用之间运行时可以共享相同的类,拥有更高的效率。而JVM机制不存在这种共享机制。不同的程序,打包以后程序都是彼此独立的,即便是他们使用了相同的类,运行时也都是单独加载和运行的。
    • JVM使用了JIT(Just In Time Compiler),而DVM早期没有使用JIT编译器。早期DVM执行代码,都需要解释器将dex代码编译成机器码,然后交给系统处理,效率不是很高。Android 2.2之后开始为DVM使用了JIT编译器,它会对多次运行的代码(热点代码)进行编译,生成相当精简的本地机器码(Native Code),这样在下次执行到相同的逻辑时,直接使用编译好的机器码即可。需要注意的是,应用程序每次重新运行的时候,都需要重做这个编译工作。

    ART虚拟机

    • 前文了解到,DVM中的应用每次运行时,字节码都需要通过JIT编译器编译为机器码,这样会使应用程序的运行效率降低。而在ART中,系统安装应用程序时会进行一次AOT(ahead of time compilation),将字节码预编译成机器码并存储在本地,这样应用程序每次运行时就不需要执行编译了,会大大增加效率。但是AOT不是完美的,它的缺点主要有两个:第一个是AOT会使安装应用的时间变长,尤其是复杂的应用。第二个是字节码预先编译成机器码,机器码需要存储空间会多一些。为了解决这两个问题,Android 7.0版本中的ART加入了JIT即时编译器,作为AOT的一个补充。应用程序安装时并不会将字节码全部编译成机器码,而是在系统运行中将热点代码编译成机器码,从而缩短应用程序安装时间,并且节省内存。

    • DVM是为32位CPU设计的,而ART是支持64位并且兼容32位CPU,这也是DVM被淘汰的主要原因之一。

    • ART对垃圾回收机制进行了改进,比如更频繁的执行并行垃圾收集,将GC暂停由2次减少为1次等等。

    • ART运行时堆空间划分和DVM不同。

    手动实现Andfix

    1. 找到要修复的方法块,修复bug代码后,对方法加上注解方便后续定位到修改的方法。
    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    //a
    public @interface Replace {
        //  目的
    //    地方  在哪里      14号   多少钱
        String clazz();
        String method();
    }
    
    1. 编译修改的Java文件生成.class,使用SDK的DX工具转成.dex文件放入手机中。
    2. APP加载dex文件,遍历出dex中所有的Class后,使用之前的注解找到要修改的function
     DexFile dexFile = DexFile.loadDex()
    Enumeration<String> entry= dexFile.entries();
    while (entry.hasMoreElements()) {
    //                全类名
      String className = entry.nextElement();
      Class realClazz=dexFile.loadClass(className, context.getClassLoader());
      if (realClazz != null) {
        fixClass(realClazz);
      }
    }
    
        private void fixClass(Class realClazz) {
    //加载方法 Method
            Method[] methods = realClazz.getMethods();
            for (Method rightMethod : methods) {
                Replace replace = rightMethod.getAnnotation(Replace.class);
                if (replace == null) {
                    continue;
                }
    
                String clazzName = replace.clazz();
                String methodName = replace.method();
    
    
                try {
                    Class wrongClazz=Class.forName(clazzName);
                    //Method     right       wrong
                    Method wrongMethod=wrongClazz.getDeclaredMethod(methodName, rightMethod.getParameterTypes());
                    replace(wrongMethod,rightMethod);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
    //调用到Native层,进行ArtMethod结构体的修改
    private native static void replace(Method wrongMethod,Method rightMethod);
    
    1. 赋值android源码中art_method.h,只保留部分声明即可。
    extern "C"
    JNIEXPORT void JNICALL
    Java_com_dongnao_andfix_DexManager_replace(JNIEnv *env, jobject instance, jint sdk,jobject wrongMethod,
                                               jobject rightMethod) {
    
    //        ArtMethod  ----->
    
        art::mirror::ArtMethod *wrong= reinterpret_cast<art::mirror::ArtMethod *>(env->FromReflectedMethod(wrongMethod));
        art::mirror::ArtMethod *right= reinterpret_cast<art::mirror::ArtMethod *>(env->FromReflectedMethod(rightMethod));
    
    //    wrong=right;
        wrong->declaring_class_ = right->declaring_class_;
        wrong->dex_cache_resolved_methods_ = right->dex_cache_resolved_methods_;
        wrong->access_flags_ = right->access_flags_;
        wrong->dex_cache_resolved_types_ = right->dex_cache_resolved_types_;
        wrong->dex_code_item_offset_ = right->dex_code_item_offset_;
        wrong->dex_method_index_ = right->dex_method_index_;
        wrong->method_index_ = right->method_index_;
    }
    

    它的思想是修改错误ArtMethod结构体中方法入口和字节码地址等,为正确ArtMethod的结构体中对应的内容。这样堆内存对象指向方法时,会去方法区找到ArtMethod,因为ArtMethod的字节码地址修改了,往栈内存压栈的栈帧就会是正确的字节码内容,从而bug得到修复。

    • 实际开发中,修复bug不需要特意在修复的方法上添加注解。Andfix提供了差分包制作工具,能够比对修改前后的apk,找出修改了的方法块,再生成一个dex,使用jadx查看可以发现,dex里面有一个该工具创建的class类,包含了修改了的方法,方法上有一个类似Replace 的注解。

    Tinker

    tinker利用Java层的ClassLoader机制,将修复包中的dex插入到系统ClassLoader的dexElements前端,系统取到了我们替换的class。修复的粒度是整个类替换。
    原理和动态加载apk一样。
    参考:https://www.jianshu.com/p/fd9ed8b720ef

    Andfix和Tinker的差异

    由于执行方法时虚拟机层都要读取方法表然后组装栈帧压栈,Andfix可以实现实时热修复。Tinker利用的是ClassLoader机制,但是当一个类已经被加载后,ClassLoader会将其缓存在内存中,不会再去读取dexElement了,所以Tinker不能实现实时热修复。

    相关文章

      网友评论

          本文标题:Andfix&Tinker 热修复方案原理

          本文链接:https://www.haomeiwen.com/subject/pzpsertx.html