认识AndFix

一、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_);

}

四、最后的超级大图

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270