美文网首页
认识AndFix

认识AndFix

作者: Utte | 来源:发表于2018-10-15 18:43 被阅读36次

    一、AndFix的简介

    在分析实现前,先大概了解一下AndFix,因为使用起来比较简单,所以就不过多介绍了。具体可以看AndFix的Github

    但是文档有这样一句.....并且AndFix已经快两年没有更新了。

    AndFix supports Android version from 2.3 to 7.0
    

    1. 主要步骤

    编码
    1. 依赖
    implementation 'com.alipay.euler:andfix:0.5.0@aar'
    
    1. 初始化PatchManager。
    mPatchManager = new PatchManager(context);
    mPatchManager.init(getVersionName(context));
    mPatchManager.loadPatch();
    
    1. 从服务端获取生成好的apatch文件,下载到本地。
    2. 从手机中加载apatch文件,和旧的apk合并。
    mPatchManager.addPatch(path);
    
    生成apatch文件
    1. 修改old.apk,生成修复好的new.apk
    2. 使用给的工具,用以下命令生成apatch文件。
    apkpatch -f <new> -t <old> -o <output> -k <keystore> -p <***> -a <alias> -e <***>
    

    如果报错的话,记得检查一下keystore天的

    1. 将apatch文件放到客户端。

    2. 热修复的实现

    看下面的图,修复实际上是将修复的方法放在patch中,用这个正确的方法去替换原有存在Bug的方法,原有的方法并没有改变、依然存在。


    image

    而方法替换的原理其实是,直接在native层进行方法的结构体信息对换,从而实现方法新旧替换,实现热修复功能。

    3. 优缺点

    优点
    1. 使用简单。
    2. 修复后不需要重启,立即生效。
    缺点
    1. 仅限于方法的替换,如果想增加类,就无能为力了。

    二、Java层源码分析

    1. PatchManager成员变量

    可以发现,在使用AndFix时,我们之和PatchManager这一个类打交道了,没有涉及到其他的类,明显是一个外观模式。

    mPatchManager = new PatchManager(context);
    

    AndFix将它所有的API都包含在了PatchManager中,就使得使用者不需要去了解其它类是什么作用。

    PatchManager中有两个重要的成员变量:

    • mAndFixManager:Bug修复、方法替换等等功能都是由AndFixManager来完成的。
    • mPatchs:在这个排序的Set中包含了应用所有的patch文件。
    // ......
    private final AndFixManager mAndFixManager;
    // ......
    private final SortedSet<Patch> mPatchs;
    // ......
    

    2. PatchManager # constructor

    在PatchManager的构造方法中,主要就是对成员变量进行初始化、创建文件夹。

    public PatchManager(Context context) {
        mContext = context;
        // 主要操作的对象
        mAndFixManager = new AndFixManager(mContext);
        // 创建文件夹 DIR为"apatch"
        mPatchDir = new File(mContext.getFilesDir(), DIR);
        // Patch对象的集合
        mPatchs = new ConcurrentSkipListSet<Patch>();
        mLoaders = new ConcurrentHashMap<String, ClassLoader>();
    }
    

    3. PatchManager # init()

    init()是我们调用的第一个方法。

    • 传入当前应用的版本号。
    • 对文件夹的判断,如果没有创建地址的目录或不是一个目录而是一个文件,就删除同名文件后直接返回。
    • 获取SharedPreference之前存储的版本号。
      • 如果不存在或者和传入版本号不一致就先清除存在的文件,再存下新的版本号。
      • 否则就表示应用没有升级,将目录下的文件添加进mPatchs中。
    public void init(String appVersion) {
        // 验证目录的有效
        if (!mPatchDir.exists() && !mPatchDir.mkdirs()) {// make directory fail
            Log.e(TAG, "patch dir create error.");
            return;
        } else if (!mPatchDir.isDirectory()) {// not directory
            mPatchDir.delete();
            return;
        }
        SharedPreferences sp = mContext.getSharedPreferences(SP_NAME,
                Context.MODE_PRIVATE);
        // 获取之前存的版本号
        String ver = sp.getString(SP_VERSION, null);
        if (ver == null || !ver.equalsIgnoreCase(appVersion)) {
            // 清空
            cleanPatch();
            // 更新版本号
            sp.edit().putString(SP_VERSION, appVersion).commit();
        } else {
            // 添加进集合mPatchs中
            initPatchs();
        }
    }
    
    3-1. PatchManager # cleanPatch()
    1. 遍历mPatchDir目录下的所有文件。
    2. 删除遍历到的文件在apatch_opt目录下的同名文件。
    3. 删除该文件。
    private void cleanPatch() {
        // mPatchDir目录下的所有文件
        File[] files = mPatchDir.listFiles();
        for (File file : files) {
            // 删除对应的opt文件
            mAndFixManager.removeOptFile(file);
            // 删除此文件
            if (!FileUtil.deleteFile(file)) {
                Log.e(TAG, file.getName() + " delete error.");
            }
        }
    }
    
    3-2. PatchManager # initPatchs()

    列出了mPatchDir目录下的所有文件,调用addPatch(file),是一个构造Patch并添加到mPatchs集合的操作。

    private void initPatchs() {
        File[] files = mPatchDir.listFiles();
        for (File file : files) {
            addPatch(file);
        }
    }
    
    3-3. PatchManager # addPatch()

    注意这里的addPatch()和我们编码调用的addPatch()是不一样的,这个是private的。这里传入一个File,判断文件是否为apatch格式,如果是就构造成Patch对象,添加到mPatchs中。

    private Patch addPatch(File file) {
        Patch patch = null;
        if (file.getName().endsWith(SUFFIX)) {
            try {
                // 构造Patch对象
                patch = new Patch(file);
                mPatchs.add(patch);
            } catch (IOException e) {
                Log.e(TAG, "addPatch", e);
            }
        }
        return patch;
    }
    

    总结来说PatchManager的init()主要是去判断版本号是否有升级,如果没有升级就将存在的Patch文件添加到mPatchs集合中。如果有升级就清空之前的文件并更新存储的版本号。

    4. Patch类

    构造方法只是将传入的File赋值大成员变量中,之后调用init()。

    public Patch(File file) throws IOException {
        mFile = file;
        init();
    }
    
    4-1 Patch # init()

    init()算是里面比较长的一个方法了。

    1. 把File转换成一个JarFile文件进行解压,读取META-INF/PATCH.MF信息。
    2. 然后开始解析Jar文件中的一些字段,这些字段都是在使用apkpatch命令生成patch文件时写入的。
    3. 判断后将patch-Classes参数放到map中,其他以-Classes结尾的对名字做前部分的截取,也放入map中。
    private void init() throws IOException {
        JarFile jarFile = null;
        InputStream inputStream = null;
        try {
            // 转换成Jar
            jarFile = new JarFile(mFile);
            // 读取META-INF/PATCH.MF
            JarEntry entry = jarFile.getJarEntry(ENTRY_NAME);
            inputStream = jarFile.getInputStream(entry);
            Manifest manifest = new Manifest(inputStream);
            // PATCH.MF文件中所有的参数
            Attributes main = manifest.getMainAttributes();
            // 开始解析字段
            // 读取Patch-Name字段,存到成员变量
            mName = main.getValue(PATCH_NAME);
            mTime = new Date(main.getValue(CREATED_TIME));
            // 初始化map
            mClassesMap = new HashMap<String, List<String>>();
            Attributes.Name attrName;
            String name;
            List<String> strings;
            // 遍历PATCH.MF中所有参数
            for (Iterator<?> it = main.keySet().iterator(); it.hasNext();) {
                attrName = (Attributes.Name) it.next();
                name = attrName.toString();
                // 参数名如果是-Classes结尾
                if (name.endsWith(CLASSES)) {
                    // 将该参数值用逗号分开
                    strings = Arrays.asList(main.getValue(attrName).split(","));
                    // 如果参数名是Patch-Classes,就将这个参数放到map中
                    if (name.equalsIgnoreCase(PATCH_CLASSES)) {
                        mClassesMap.put(mName, strings);
                    } else {
                        // 如果不是Patch-Classes,就把name的Classes去了放到map中
                        mClassesMap.put(
                                name.trim().substring(0, name.length() - 8),// remove "-Classes"
                                strings);
                    }
                }
            }
        } finally {
            // 关闭资源
            if (jarFile != null) {
                jarFile.close();
            }
            if (inputStream != null) {
                inputStream.close();
            }
        }
    }
    

    其实每个apatch都是一个JarFile文件,可以解压打开,打开后外面是一个classes.dex文件和一个META-INF文件夹,这个文件的名字就很熟悉了,因为在上面的方法中出现过。META-INF文件夹中果然有PATCH.MF文件,对应与于上面代码中的entry。


    至于其中PATCH.MF文件的结构类似于下图,Patch-Classes字段存储着需要热修复的类,类名之后加上了_CF后缀,各个以逗号隔开,只不过这里只有一个类。

    5. PatchManager # loadPatch()

    在调用完PatchManager的init()后,需要调用无参loadPatch()。

    1. 遍历mPatchs列表中所有的Patch对象。
    2. 拿到patch对象中所有的class文件。
    3. 传入每个class,调用AndFixManager的fix()。
    public void loadPatch() {
        mLoaders.put("*", mContext.getClassLoader());// wildcard
        Set<String> patchNames;
        List<String> classes;
        for (Patch patch : mPatchs) {
            patchNames = patch.getPatchNames();
            for (String patchName : patchNames) {
                classes = patch.getClasses(patchName);
                mAndFixManager.fix(patch.getFile(), mContext.getClassLoader(),
                        classes);
            }
        }
    }
    

    6. PatchManager # addPatch()

    我们编程时最终是调用这个方法去执行修复的。

    1. 创建文件并做判断。
    2. 将patch文件从原目录复制到专门放patch的目录下。
    3. 调用addPatch(File file),解析成Patch对象,放入mPatchs集合中。
    4. 调用有参loadPatch()去完成加载patch文件。
    public void addPatch(String path) throws IOException {
        File src = new File(path);
        File dest = new File(mPatchDir, src.getName());
        if(!src.exists()){
            throw new FileNotFoundException(path);
        }
        if (dest.exists()) {
            Log.d(TAG, "patch [" + path + "] has be loaded.");
            return;
        }
        FileUtil.copyFile(src, dest);// copy to patch's directory
        Patch patch = addPatch(dest);
        if (patch != null) {
            loadPatch(patch);
        }
    }
    

    6-1 PatchManager # loadPatch()

    和上面说过的无参loadPatch()不同的是这个loadPatch()只会去传入参数Patch对象中的class文件调用AndFixManager的fix()。

    private void loadPatch(Patch patch) {
        Set<String> patchNames = patch.getPatchNames();
        ClassLoader cl;
        List<String> classes;
        for (String patchName : patchNames) {
            if (mLoaders.containsKey("*")) {
                cl = mContext.getClassLoader();
            } else {
                cl = mLoaders.get(patchName);
            }
            if (cl != null) {
                classes = patch.getClasses(patchName);
                mAndFixManager.fix(patch.getFile(), cl, classes);
            }
        }
    }
    

    也能看出来PatchManager只是对Patch文件进行管理,并不涉及到方法替换的实现,真正的实现还需要去看AndFixManager。

    7. AndFixManager

    构造方法

    在构造方法中主要干了三件事:

    1. 判断当前环境是否支持热修复。
    2. 初始化验证对象。
    3. 验证目录有效性。
    public AndFixManager(Context context) {
        mContext = context;
        // 判断当前环境
        mSupport = Compat.isSupport();
        if (mSupport) {
            // 验证对象
            mSecurityChecker = new SecurityChecker(mContext);
            // 判断目录
            mOptDir = new File(mContext.getFilesDir(), DIR);
            if (!mOptDir.exists() && !mOptDir.mkdirs()) {// make directory fail
                mSupport = false;
                Log.e(TAG, "opt dir create error.");
            } else if (!mOptDir.isDirectory()) {// not directory
                mOptDir.delete();
                mSupport = false;
            }
        }
    }
    

    然后来看一看是怎么判断当前环境的有效性的。有以下要求:

    1. 不是阿里的YunOS。
    2. AndFix在native层设置成功。
    3. Android在2.3到7.0版本
    public static synchronized boolean isSupport() {
        if (isChecked)
            return isSupport;
        isChecked = true;
        // not support alibaba's YunOs
        if (!isYunOS() && AndFix.setup() && isSupportSDKVersion()) {
            isSupport = true;
        }
        if (inBlackList()) {
            isSupport = false;
        }
        return isSupport;
    }
    
    7-1 AndFixManager # fix()
    1. 进行一些验证工作,对比修复包的签名与应用的签名是否一致,如果不通过就直接返回。
    2. 用DexFile格式来加载修复包。
    3. 遍历DexFile,找到需要修复的类,获得其字节码,调用fixClass()。
    public synchronized void fix(File file, ClassLoader classLoader,
            List<String> classes) {
        if (!mSupport) {
            return;
        }
        // 检查签名
        if (!mSecurityChecker.verifyApk(file)) {// security check fail
            return;
        }
        try {
            File optfile = new File(mOptDir, file.getName());
            boolean saveFingerprint = true;
            if (optfile.exists()) {
                if (mSecurityChecker.verifyOpt(optfile)) {
                    saveFingerprint = false;
                } else if (!optfile.delete()) {
                    return;
                }
            }
            // 用DexFile来加载修复包文件
            final DexFile dexFile = DexFile.loadDex(file.getAbsolutePath(),
                    optfile.getAbsolutePath(), Context.MODE_PRIVATE);
            if (saveFingerprint) {
                mSecurityChecker.saveOptSig(optfile);
            }
            // 定义了一个ClassLoader,实现了自己的findClass()逻辑
            ClassLoader patchClassLoader = new ClassLoader(classLoader) {
                @Override
                protected Class<?> findClass(String className)
                        throws ClassNotFoundException {
                    // 使用了DexFile的loadClass(),其实这个自定义ClassLoader就和DexClassLoader一样,因为DexClassLoader也是使用DexFile去操作的。
                    Class<?> clazz = dexFile.loadClass(className, this);
                    if (clazz == null
                            && className.startsWith("com.alipay.euler.andfix")) {
                        return Class.forName(className);// annotation’s class
                                                        // not found
                    }
                    if (clazz == null) {
                        throw new ClassNotFoundException(className);
                    }
                    return clazz;
                }
            };
            Enumeration<String> entrys = dexFile.entries();
            Class<?> clazz = null;
            // 循环遍历DexFile
            while (entrys.hasMoreElements()) {
                String entry = entrys.nextElement();
                if (classes != null && !classes.contains(entry)) {
                    continue;// skip, not need fix
                }
                // 获得真正要修改的类的字节码
                clazz = dexFile.loadClass(entry, patchClassLoader);
                if (clazz != null) {
                    // 传入字节码进行修复
                    fixClass(clazz, classLoader);
                }
            }
        } catch (IOException e) {
            Log.e(TAG, "pacth", e);
        }
    }
    
    7-2 AndFixManager # fixClass()
    1. 遍历类中所有方法。
    2. 判断方法上有没有MethodReplace注解,如果有就调用replaceMethod替换方法。
    private void fixClass(Class<?> clazz, ClassLoader classLoader) {
        // 反射获取到类中所有的方法
        Method[] methods = clazz.getDeclaredMethods();
        // 一个注解
        MethodReplace methodReplace;
        String clz;
        String meth;
        // 遍历方法
        for (Method method : methods) {
            // 判断每个方法上有没有加MethodReplace注解
            methodReplace = method.getAnnotation(MethodReplace.class);
            if (methodReplace == null)
                continue;
            // 如果有此注解就记录下信息
            clz = methodReplace.clazz();
            meth = methodReplace.method();
            // 信息不为空就调用replaceMethod()完成方法的替换
            if (!isEmpty(clz) && !isEmpty(meth)) {
                replaceMethod(classLoader, clz, meth, method);
            }
        }
    }
    
    7-3 AndFixManager # replaceMethod()

    调用了AndFix的addReplaceMethod()。

    private void replaceMethod(ClassLoader classLoader, String clz,
            String meth, Method method) {
        try {
            String key = clz + "@" + classLoader.toString();
            Class<?> clazz = mFixedClass.get(key);
            if (clazz == null) {// class not load
                Class<?> clzz = classLoader.loadClass(clz);
                // initialize target class
                clazz = AndFix.initTargetClass(clzz);
            }
            if (clazz != null) {// initialize class OK
                mFixedClass.put(key, clazz);
                Method src = clazz.getDeclaredMethod(meth,
                        method.getParameterTypes());
                AndFix.addReplaceMethod(src, method);
            }
        } catch (Exception e) {
            Log.e(TAG, "replaceMethod", e);
        }
    }
    

    AndFix的addReplaceMethod()又调用了replaceMethod()

    public static void addReplaceMethod(Method src, Method dest) {
        try {
            replaceMethod(src, dest);
            initFields(dest.getDeclaringClass());
        } catch (Throwable e) {
            Log.e(TAG, "addReplaceMethod", e);
        }
    }
    

    最终这是一个native方法,通过C/C++对dex文件进行了一些操作达到方法替换的任务。

    private static native void replaceMethod(Method dest, Method src);
    

    三、Native层源码分析

    通过上面的分析可以发现其实Java只调用了三个native方法:

    • 一个是在检测环境是否支持热修复后调用的setUp()。
    • 一个是在replaceMethod()前后调用的setFieldFlag()。
    • 还有就是最最重要的replaceMethod()了。

    1. AndFix的分类调用

    整个native层的代码并不多,但是分了两种情况去分别处理,一个是art虚拟机的一个是dalvik虚拟机的。AndFix类的native层也主要就是起到一个分类调用的工作。

    andFix.cpp # setup()
    static jboolean setup(JNIEnv* env, jclass clazz, jboolean isart,
            jint apilevel) {
        isArt = isart;
        LOGD("vm is: %s , apilevel is: %i", (isArt ? "art" : "dalvik"),
                (int )apilevel);
        if (isArt) {
            return art_setup(env, (int) apilevel);
        } else {
            return dalvik_setup(env, (int) apilevel);
        }
    }
    
    andFix.cpp # replaceMethod()
    static void replaceMethod(JNIEnv* env, jclass clazz, jobject src,
            jobject dest) {
        if (isArt) {
            art_replaceMethod(env, src, dest);
        } else {
            dalvik_replaceMethod(env, src, dest);
        }
    }
    
    andFix.cpp # setFieldFlag()
    static void setFieldFlag(JNIEnv* env, jclass clazz, jobject field) {
        if (isArt) {
            art_setFieldFlag(env, field);
        } else {
            dalvik_setFieldFlag(env, field);
        }
    }
    

    上面三个方法都是先去判断了是否是art虚拟机环境,之后分别调用不同虚拟机的不同实现方法。isArt是在setUp()中通过传入的参数设置的。

    2. dalvik环境下的实现

    只有一个头文件和一个cpp文件。


    分别来看三个方法。

    dalvik_method_replace.cpp # dalvik_setup()

    这个方法看名字也知道就是为了之后的工作做准备的,主要做了三件事:

    1. 获取dvmDecodeIndirectRef_fnPrt指针。
    2. 获取dvmThreadSelf_fnPtr指针。
    3. 获取Method类中的getDeclaringClass方法。
    extern jboolean __attribute__ ((visibility ("hidden"))) dalvik_setup(
            JNIEnv* env, int apilevel) {
        // 加载系统库libdvm.so文件
        void* dvm_hand = dlopen("libdvm.so", RTLD_NOW);
        // 如果加载成功
        if (dvm_hand) {
            // 根据api版本去获取dvmDecodeIndirectRef_fnPrt这个指针
            dvmDecodeIndirectRef_fnPtr = dvm_dlsym(dvm_hand,
                    apilevel > 10 ?
                            "_Z20dvmDecodeIndirectRefP6ThreadP8_jobject" :
                            "dvmDecodeIndirectRef");
            // 获取失败就返回false
            if (!dvmDecodeIndirectRef_fnPtr) {
                return JNI_FALSE;
            }
            // 再获取dvmThreadSelf_fnPtr这个指针
            dvmThreadSelf_fnPtr = dvm_dlsym(dvm_hand,
                    apilevel > 10 ? "_Z13dvmThreadSelfv" : "dvmThreadSelf");
            // 失败就返回false
            if (!dvmThreadSelf_fnPtr) {
                return JNI_FALSE;
            }
            // 获取Method
            jclass clazz = env->FindClass("java/lang/reflect/Method");
            // 获取Method类中的getDeclaringClass方法
            jClassMethod = env->GetMethodID(clazz, "getDeclaringClass",
                            "()Ljava/lang/Class;");
    
            return JNI_TRUE;
        } else {
            // 如果加载失败就直接返回false
            return JNI_FALSE;
        }
    }
    
    dalvik_method_replace.cpp # dalvik_setFieldFlag()

    这个方法比较简单就是去修改accessFlags,将这个field的访问权限变成public的。

    extern void dalvik_setFieldFlag(JNIEnv* env, jobject field) {
        Field* dalvikField = (Field*) env->FromReflectedField(field);
        dalvikField->accessFlags = dalvikField->accessFlags & (~ACC_PRIVATE)
                | ACC_PUBLIC;
        LOGD("dalvik_setFieldFlag: %d ", dalvikField->accessFlags);
    }
    
    dalvik_method_replace.cpp # dalvik_replaceMethod()

    真正方法替换的实现。

    extern void __attribute__ ((visibility ("hidden"))) dalvik_replaceMethod(
            JNIEnv* env, jobject src, jobject dest) {
        // 通过修复方法获取到class对象
        jobject clazz = env->CallObjectMethod(dest, jClassMethod);
        // 获取指向ClassObject结构体的指针
        ClassObject* clz = (ClassObject*) dvmDecodeIndirectRef_fnPtr(
                dvmThreadSelf_fnPtr(), clazz);
        // 改变状态为初始化完毕
        clz->status = CLASS_INITIALIZED;
    
        // 获取新旧方法结构体指针
        Method* meth = (Method*) env->FromReflectedMethod(src);
        Method* target = (Method*) env->FromReflectedMethod(dest);
        LOGD("dalvikMethod: %s", meth->name);
    
    //  meth->clazz = target->clazz;
        // 开始新旧方法结构体内容的替换
        meth->accessFlags |= ACC_PUBLIC;
        meth->methodIndex = target->methodIndex;
        meth->jniArgInfo = target->jniArgInfo;
        meth->registersSize = target->registersSize;
        meth->outsSize = target->outsSize;
        meth->insSize = target->insSize;
    
        meth->prototype = target->prototype;
        meth->insns = target->insns;
        meth->nativeFunc = target->nativeFunc;
    }
    

    3. art环境下的实现

    和dalvik不同的是art目录下又分了各种android版本号的不同实现,所以如果设备是art虚拟机环境,不同Android版本会有不同。


    art_method_replace.cpp # art_setup()

    只是记录了传入的版本号,比dalvik要简单。

    extern jboolean __attribute__ ((visibility ("hidden"))) art_setup(JNIEnv* env,
            int level) {
        apilevel = level;
        return JNI_TRUE;
    }
    
    art_method_replace.cpp # art_setFieldFlag()

    修改访问范围的方法就需要根据版本去调用不同方法了。

    extern void __attribute__ ((visibility ("hidden"))) art_setFieldFlag(
            JNIEnv* env, jobject field) {
        if (apilevel > 23) {
            setFieldFlag_7_0(env, field);
        } else if (apilevel > 22) {
            setFieldFlag_6_0(env, field);
        } else if (apilevel > 21) {
            setFieldFlag_5_1(env, field);
        } else  if (apilevel > 19) {
            setFieldFlag_5_0(env, field);
        }else{
            setFieldFlag_4_4(env, field);
        }
    }
    

    先来看看setFieldFlag_7_0(),实现逻辑和dalvik是一样的,都是通过FromReflectedField来获取field,然后修改accessFlag来修改访问权限,而dalvik里面用得是常量名,这里直接把private、public的值放上去了。

    void setFieldFlag_7_0(JNIEnv* env, jobject field) {
        // 获取到field
        art::mirror::ArtField* artField =
                (art::mirror::ArtField*) env->FromReflectedField(field);
        // 修改accessFlag
        artField->access_flags_ = artField->access_flags_ & (~0x0002) | 0x0001;
        LOGD("setFieldFlag_7_0: %d ", artField->access_flags_);
    }
    

    再看setFieldFlag_6_0()

    void setFieldFlag_6_0(JNIEnv* env, jobject field) {
        art::mirror::ArtField* artField =
                (art::mirror::ArtField*) env->FromReflectedField(field);
        artField->access_flags_ = artField->access_flags_ & (~0x0002) | 0x0001;
        LOGD("setFieldFlag_6_0: %d ", artField->access_flags_);
    }
    

    除了函数名和最后一句输出以外没有任何区别?????然后我发现每个版本都是一样的,只是函数名和输出有区别,有点迷......

    art_method_replace.cpp # art_replaceMethod

    替换方法的这个函数同样需要区分版本号。

    extern void __attribute__ ((visibility ("hidden"))) art_replaceMethod(
            JNIEnv* env, jobject src, jobject dest) {
        if (apilevel > 23) {
            replace_7_0(env, src, dest);
        } else if (apilevel > 22) {
            replace_6_0(env, src, dest);
        } else if (apilevel > 21) {
            replace_5_1(env, src, dest);
        } else if (apilevel > 19) {
            replace_5_0(env, src, dest);
        }else{
            replace_4_4(env, src, dest);
        }
    }
    

    先看7.0,和dalvik还是一样的逻辑。至于其它版本的实现,都是差不多的,不过每个版本替换的内容有一点差异。

    void replace_7_0(JNIEnv* env, jobject src, jobject dest) {
        // 获取旧新方法的结构体
        art::mirror::ArtMethod* smeth =
                (art::mirror::ArtMethod*) env->FromReflectedMethod(src);
        art::mirror::ArtMethod* dmeth =
                (art::mirror::ArtMethod*) env->FromReflectedMethod(dest);
    
        // 开始结构体内容替换
        reinterpret_cast<art::mirror::Class*>(dmeth->declaring_class_)->clinit_thread_id_ =
                reinterpret_cast<art::mirror::Class*>(smeth->declaring_class_)->clinit_thread_id_;
        reinterpret_cast<art::mirror::Class*>(dmeth->declaring_class_)->status_ =
                reinterpret_cast<art::mirror::Class*>(smeth->declaring_class_)->status_ -1;
        //for reflection invoke
        reinterpret_cast<art::mirror::Class*>(dmeth->declaring_class_)->super_class_ = 0;
    
        smeth->declaring_class_ = dmeth->declaring_class_;
        smeth->access_flags_ = dmeth->access_flags_  | 0x0001;
        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_;
    
        LOGD("replace_7_0: %d , %d",
                smeth->ptr_sized_fields_.entry_point_from_quick_compiled_code_,
                dmeth->ptr_sized_fields_.entry_point_from_quick_compiled_code_);
    
    }
    

    四、最后的超级大图

    相关文章

      网友评论

          本文标题:认识AndFix

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