认识AndFix
一、AndFix的简介
在分析实现前,先大概了解一下AndFix,因为使用起来比较简单,所以就不过多介绍了。具体可以看AndFix的Github。
但是文档有这样一句.....并且AndFix已经快两年没有更新了。
AndFix supports Android version from 2.3 to 7.0
1. 主要步骤
编码
- 依赖
implementation 'com.alipay.euler:andfix:0.5.0@aar'
- 初始化PatchManager。
mPatchManager = new PatchManager(context);
mPatchManager.init(getVersionName(context));
mPatchManager.loadPatch();
- 从服务端获取生成好的apatch文件,下载到本地。
- 从手机中加载apatch文件,和旧的apk合并。
mPatchManager.addPatch(path);
生成apatch文件
- 修改old.apk,生成修复好的new.apk
- 使用给的工具,用以下命令生成apatch文件。
apkpatch -f <new> -t <old> -o <output> -k <keystore> -p <***> -a <alias> -e <***>
如果报错的话,记得检查一下keystore天的
- 将apatch文件放到客户端。
2. 热修复的实现
看下面的图,修复实际上是将修复的方法放在patch中,用这个正确的方法去替换原有存在Bug的方法,原有的方法并没有改变、依然存在。
image
而方法替换的原理其实是,直接在native层进行方法的结构体信息对换,从而实现方法新旧替换,实现热修复功能。
3. 优缺点
优点
- 使用简单。
- 修复后不需要重启,立即生效。
缺点
- 仅限于方法的替换,如果想增加类,就无能为力了。
二、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()
- 遍历mPatchDir目录下的所有文件。
- 删除遍历到的文件在apatch_opt目录下的同名文件。
- 删除该文件。
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()算是里面比较长的一个方法了。
- 把File转换成一个JarFile文件进行解压,读取META-INF/PATCH.MF信息。
- 然后开始解析Jar文件中的一些字段,这些字段都是在使用apkpatch命令生成patch文件时写入的。
- 判断后将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()。
- 遍历mPatchs列表中所有的Patch对象。
- 拿到patch对象中所有的class文件。
- 传入每个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()
我们编程时最终是调用这个方法去执行修复的。
- 创建文件并做判断。
- 将patch文件从原目录复制到专门放patch的目录下。
- 调用addPatch(File file),解析成Patch对象,放入mPatchs集合中。
- 调用有参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
构造方法
在构造方法中主要干了三件事:
- 判断当前环境是否支持热修复。
- 初始化验证对象。
- 验证目录有效性。
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;
}
}
}
然后来看一看是怎么判断当前环境的有效性的。有以下要求:
- 不是阿里的YunOS。
- AndFix在native层设置成功。
- 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()
- 进行一些验证工作,对比修复包的签名与应用的签名是否一致,如果不通过就直接返回。
- 用DexFile格式来加载修复包。
- 遍历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()
- 遍历类中所有方法。
- 判断方法上有没有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()
这个方法看名字也知道就是为了之后的工作做准备的,主要做了三件事:
- 获取dvmDecodeIndirectRef_fnPrt指针。
- 获取dvmThreadSelf_fnPtr指针。
- 获取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_);
}