一、AndFix的簡介
在分析實(shí)現(xiàn)前,先大概了解一下AndFix,因?yàn)槭褂闷饋肀容^簡單,所以就不過多介紹了。具體可以看AndFix的Github。
但是文檔有這樣一句.....并且AndFix已經(jīng)快兩年沒有更新了。
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();
- 從服務(wù)端獲取生成好的apatch文件,下載到本地。
- 從手機(jī)中加載apatch文件,和舊的apk合并。
mPatchManager.addPatch(path);
生成apatch文件
- 修改old.apk,生成修復(fù)好的new.apk
- 使用給的工具,用以下命令生成apatch文件。
apkpatch -f <new> -t <old> -o <output> -k <keystore> -p <***> -a <alias> -e <***>
如果報(bào)錯(cuò)的話,記得檢查一下keystore天的
- 將apatch文件放到客戶端。
2. 熱修復(fù)的實(shí)現(xiàn)
看下面的圖,修復(fù)實(shí)際上是將修復(fù)的方法放在patch中,用這個(gè)正確的方法去替換原有存在Bug的方法,原有的方法并沒有改變、依然存在。

而方法替換的原理其實(shí)是,直接在native層進(jìn)行方法的結(jié)構(gòu)體信息對(duì)換,從而實(shí)現(xiàn)方法新舊替換,實(shí)現(xiàn)熱修復(fù)功能。
3. 優(yōu)缺點(diǎn)
優(yōu)點(diǎn)
- 使用簡單。
- 修復(fù)后不需要重啟,立即生效。
缺點(diǎn)
- 僅限于方法的替換,如果想增加類,就無能為力了。
二、Java層源碼分析
1. PatchManager成員變量
可以發(fā)現(xiàn),在使用AndFix時(shí),我們之和PatchManager這一個(gè)類打交道了,沒有涉及到其他的類,明顯是一個(gè)外觀模式。
mPatchManager = new PatchManager(context);
AndFix將它所有的API都包含在了PatchManager中,就使得使用者不需要去了解其它類是什么作用。
PatchManager中有兩個(gè)重要的成員變量:
- mAndFixManager:Bug修復(fù)、方法替換等等功能都是由AndFixManager來完成的。
- mPatchs:在這個(gè)排序的Set中包含了應(yīng)用所有的patch文件。
// ......
private final AndFixManager mAndFixManager;
// ......
private final SortedSet<Patch> mPatchs;
// ......
2. PatchManager # constructor
在PatchManager的構(gòu)造方法中,主要就是對(duì)成員變量進(jìn)行初始化、創(chuàng)建文件夾。
public PatchManager(Context context) {
mContext = context;
// 主要操作的對(duì)象
mAndFixManager = new AndFixManager(mContext);
// 創(chuàng)建文件夾 DIR為"apatch"
mPatchDir = new File(mContext.getFilesDir(), DIR);
// Patch對(duì)象的集合
mPatchs = new ConcurrentSkipListSet<Patch>();
mLoaders = new ConcurrentHashMap<String, ClassLoader>();
}
3. PatchManager # init()
init()是我們調(diào)用的第一個(gè)方法。
- 傳入當(dāng)前應(yīng)用的版本號(hào)。
- 對(duì)文件夾的判斷,如果沒有創(chuàng)建地址的目錄或不是一個(gè)目錄而是一個(gè)文件,就刪除同名文件后直接返回。
- 獲取SharedPreference之前存儲(chǔ)的版本號(hào)。
- 如果不存在或者和傳入版本號(hào)不一致就先清除存在的文件,再存下新的版本號(hào)。
- 否則就表示應(yīng)用沒有升級(jí),將目錄下的文件添加進(jìn)mPatchs中。
public void init(String appVersion) {
// 驗(yàn)證目錄的有效
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);
// 獲取之前存的版本號(hào)
String ver = sp.getString(SP_VERSION, null);
if (ver == null || !ver.equalsIgnoreCase(appVersion)) {
// 清空
cleanPatch();
// 更新版本號(hào)
sp.edit().putString(SP_VERSION, appVersion).commit();
} else {
// 添加進(jìn)集合mPatchs中
initPatchs();
}
}
3-1. PatchManager # cleanPatch()
- 遍歷mPatchDir目錄下的所有文件。
- 刪除遍歷到的文件在apatch_opt目錄下的同名文件。
- 刪除該文件。
private void cleanPatch() {
// mPatchDir目錄下的所有文件
File[] files = mPatchDir.listFiles();
for (File file : files) {
// 刪除對(duì)應(yīng)的opt文件
mAndFixManager.removeOptFile(file);
// 刪除此文件
if (!FileUtil.deleteFile(file)) {
Log.e(TAG, file.getName() + " delete error.");
}
}
}
3-2. PatchManager # initPatchs()
列出了mPatchDir目錄下的所有文件,調(diào)用addPatch(file),是一個(gè)構(gòu)造Patch并添加到mPatchs集合的操作。
private void initPatchs() {
File[] files = mPatchDir.listFiles();
for (File file : files) {
addPatch(file);
}
}
3-3. PatchManager # addPatch()
注意這里的addPatch()和我們編碼調(diào)用的addPatch()是不一樣的,這個(gè)是private的。這里傳入一個(gè)File,判斷文件是否為apatch格式,如果是就構(gòu)造成Patch對(duì)象,添加到mPatchs中。
private Patch addPatch(File file) {
Patch patch = null;
if (file.getName().endsWith(SUFFIX)) {
try {
// 構(gòu)造Patch對(duì)象
patch = new Patch(file);
mPatchs.add(patch);
} catch (IOException e) {
Log.e(TAG, "addPatch", e);
}
}
return patch;
}
總結(jié)來說PatchManager的init()主要是去判斷版本號(hào)是否有升級(jí),如果沒有升級(jí)就將存在的Patch文件添加到mPatchs集合中。如果有升級(jí)就清空之前的文件并更新存儲(chǔ)的版本號(hào)。
4. Patch類
構(gòu)造方法只是將傳入的File賦值大成員變量中,之后調(diào)用init()。
public Patch(File file) throws IOException {
mFile = file;
init();
}
4-1 Patch # init()
init()算是里面比較長的一個(gè)方法了。
- 把File轉(zhuǎn)換成一個(gè)JarFile文件進(jìn)行解壓,讀取META-INF/PATCH.MF信息。
- 然后開始解析Jar文件中的一些字段,這些字段都是在使用apkpatch命令生成patch文件時(shí)寫入的。
- 判斷后將patch-Classes參數(shù)放到map中,其他以-Classes結(jié)尾的對(duì)名字做前部分的截取,也放入map中。
private void init() throws IOException {
JarFile jarFile = null;
InputStream inputStream = null;
try {
// 轉(zhuǎn)換成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文件中所有的參數(shù)
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中所有參數(shù)
for (Iterator<?> it = main.keySet().iterator(); it.hasNext();) {
attrName = (Attributes.Name) it.next();
name = attrName.toString();
// 參數(shù)名如果是-Classes結(jié)尾
if (name.endsWith(CLASSES)) {
// 將該參數(shù)值用逗號(hào)分開
strings = Arrays.asList(main.getValue(attrName).split(","));
// 如果參數(shù)名是Patch-Classes,就將這個(gè)參數(shù)放到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 {
// 關(guān)閉資源
if (jarFile != null) {
jarFile.close();
}
if (inputStream != null) {
inputStream.close();
}
}
}
其實(shí)每個(gè)apatch都是一個(gè)JarFile文件,可以解壓打開,打開后外面是一個(gè)classes.dex文件和一個(gè)META-INF文件夾,這個(gè)文件的名字就很熟悉了,因?yàn)樵谏厦娴姆椒ㄖ谐霈F(xiàn)過。META-INF文件夾中果然有PATCH.MF文件,對(duì)應(yīng)與于上面代碼中的entry。


至于其中PATCH.MF文件的結(jié)構(gòu)類似于下圖,Patch-Classes字段存儲(chǔ)著需要熱修復(fù)的類,類名之后加上了_CF后綴,各個(gè)以逗號(hào)隔開,只不過這里只有一個(gè)類。

5. PatchManager # loadPatch()
在調(diào)用完P(guān)atchManager的init()后,需要調(diào)用無參loadPatch()。
- 遍歷mPatchs列表中所有的Patch對(duì)象。
- 拿到patch對(duì)象中所有的class文件。
- 傳入每個(gè)class,調(diào)用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()
我們編程時(shí)最終是調(diào)用這個(gè)方法去執(zhí)行修復(fù)的。
- 創(chuàng)建文件并做判斷。
- 將patch文件從原目錄復(fù)制到專門放patch的目錄下。
- 調(diào)用addPatch(File file),解析成Patch對(duì)象,放入mPatchs集合中。
- 調(diào)用有參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()不同的是這個(gè)loadPatch()只會(huì)去傳入?yún)?shù)Patch對(duì)象中的class文件調(diào)用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只是對(duì)Patch文件進(jìn)行管理,并不涉及到方法替換的實(shí)現(xiàn),真正的實(shí)現(xiàn)還需要去看AndFixManager。
7. AndFixManager
構(gòu)造方法
在構(gòu)造方法中主要干了三件事:
- 判斷當(dāng)前環(huán)境是否支持熱修復(fù)。
- 初始化驗(yàn)證對(duì)象。
- 驗(yàn)證目錄有效性。
public AndFixManager(Context context) {
mContext = context;
// 判斷當(dāng)前環(huán)境
mSupport = Compat.isSupport();
if (mSupport) {
// 驗(yàn)證對(duì)象
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;
}
}
}
然后來看一看是怎么判斷當(dāng)前環(huán)境的有效性的。有以下要求:
- 不是阿里的YunOS。
- AndFix在native層設(shè)置成功。
- 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()
- 進(jìn)行一些驗(yàn)證工作,對(duì)比修復(fù)包的簽名與應(yīng)用的簽名是否一致,如果不通過就直接返回。
- 用DexFile格式來加載修復(fù)包。
- 遍歷DexFile,找到需要修復(fù)的類,獲得其字節(jié)碼,調(diào)用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來加載修復(fù)包文件
final DexFile dexFile = DexFile.loadDex(file.getAbsolutePath(),
optfile.getAbsolutePath(), Context.MODE_PRIVATE);
if (saveFingerprint) {
mSecurityChecker.saveOptSig(optfile);
}
// 定義了一個(gè)ClassLoader,實(shí)現(xiàn)了自己的findClass()邏輯
ClassLoader patchClassLoader = new ClassLoader(classLoader) {
@Override
protected Class<?> findClass(String className)
throws ClassNotFoundException {
// 使用了DexFile的loadClass(),其實(shí)這個(gè)自定義ClassLoader就和DexClassLoader一樣,因?yàn)镈exClassLoader也是使用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;
// 循環(huán)遍歷DexFile
while (entrys.hasMoreElements()) {
String entry = entrys.nextElement();
if (classes != null && !classes.contains(entry)) {
continue;// skip, not need fix
}
// 獲得真正要修改的類的字節(jié)碼
clazz = dexFile.loadClass(entry, patchClassLoader);
if (clazz != null) {
// 傳入字節(jié)碼進(jìn)行修復(fù)
fixClass(clazz, classLoader);
}
}
} catch (IOException e) {
Log.e(TAG, "pacth", e);
}
}
7-2 AndFixManager # fixClass()
- 遍歷類中所有方法。
- 判斷方法上有沒有MethodReplace注解,如果有就調(diào)用replaceMethod替換方法。
private void fixClass(Class<?> clazz, ClassLoader classLoader) {
// 反射獲取到類中所有的方法
Method[] methods = clazz.getDeclaredMethods();
// 一個(gè)注解
MethodReplace methodReplace;
String clz;
String meth;
// 遍歷方法
for (Method method : methods) {
// 判斷每個(gè)方法上有沒有加MethodReplace注解
methodReplace = method.getAnnotation(MethodReplace.class);
if (methodReplace == null)
continue;
// 如果有此注解就記錄下信息
clz = methodReplace.clazz();
meth = methodReplace.method();
// 信息不為空就調(diào)用replaceMethod()完成方法的替換
if (!isEmpty(clz) && !isEmpty(meth)) {
replaceMethod(classLoader, clz, meth, method);
}
}
}
7-3 AndFixManager # replaceMethod()
調(diào)用了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()又調(diào)用了replaceMethod()
public static void addReplaceMethod(Method src, Method dest) {
try {
replaceMethod(src, dest);
initFields(dest.getDeclaringClass());
} catch (Throwable e) {
Log.e(TAG, "addReplaceMethod", e);
}
}
最終這是一個(gè)native方法,通過C/C++對(duì)dex文件進(jìn)行了一些操作達(dá)到方法替換的任務(wù)。
private static native void replaceMethod(Method dest, Method src);
三、Native層源碼分析
通過上面的分析可以發(fā)現(xiàn)其實(shí)Java只調(diào)用了三個(gè)native方法:
- 一個(gè)是在檢測(cè)環(huán)境是否支持熱修復(fù)后調(diào)用的setUp()。
- 一個(gè)是在replaceMethod()前后調(diào)用的setFieldFlag()。
- 還有就是最最重要的replaceMethod()了。
1. AndFix的分類調(diào)用
整個(gè)native層的代碼并不多,但是分了兩種情況去分別處理,一個(gè)是art虛擬機(jī)的一個(gè)是dalvik虛擬機(jī)的。AndFix類的native層也主要就是起到一個(gè)分類調(diào)用的工作。
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);
}
}
上面三個(gè)方法都是先去判斷了是否是art虛擬機(jī)環(huán)境,之后分別調(diào)用不同虛擬機(jī)的不同實(shí)現(xiàn)方法。isArt是在setUp()中通過傳入的參數(shù)設(shè)置的。
2. dalvik環(huán)境下的實(shí)現(xiàn)
只有一個(gè)頭文件和一個(gè)cpp文件。

分別來看三個(gè)方法。
dalvik_method_replace.cpp # dalvik_setup()
這個(gè)方法看名字也知道就是為了之后的工作做準(zhǔn)備的,主要做了三件事:
- 獲取dvmDecodeIndirectRef_fnPrt指針。
- 獲取dvmThreadSelf_fnPtr指針。
- 獲取Method類中的getDeclaringClass方法。
extern jboolean __attribute__ ((visibility ("hidden"))) dalvik_setup(
JNIEnv* env, int apilevel) {
// 加載系統(tǒng)庫libdvm.so文件
void* dvm_hand = dlopen("libdvm.so", RTLD_NOW);
// 如果加載成功
if (dvm_hand) {
// 根據(jù)api版本去獲取dvmDecodeIndirectRef_fnPrt這個(gè)指針
dvmDecodeIndirectRef_fnPtr = dvm_dlsym(dvm_hand,
apilevel > 10 ?
"_Z20dvmDecodeIndirectRefP6ThreadP8_jobject" :
"dvmDecodeIndirectRef");
// 獲取失敗就返回false
if (!dvmDecodeIndirectRef_fnPtr) {
return JNI_FALSE;
}
// 再獲取dvmThreadSelf_fnPtr這個(gè)指針
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()
這個(gè)方法比較簡單就是去修改accessFlags,將這個(gè)field的訪問權(quán)限變成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()
真正方法替換的實(shí)現(xiàn)。
extern void __attribute__ ((visibility ("hidden"))) dalvik_replaceMethod(
JNIEnv* env, jobject src, jobject dest) {
// 通過修復(fù)方法獲取到class對(duì)象
jobject clazz = env->CallObjectMethod(dest, jClassMethod);
// 獲取指向ClassObject結(jié)構(gòu)體的指針
ClassObject* clz = (ClassObject*) dvmDecodeIndirectRef_fnPtr(
dvmThreadSelf_fnPtr(), clazz);
// 改變狀態(tài)為初始化完畢
clz->status = CLASS_INITIALIZED;
// 獲取新舊方法結(jié)構(gòu)體指針
Method* meth = (Method*) env->FromReflectedMethod(src);
Method* target = (Method*) env->FromReflectedMethod(dest);
LOGD("dalvikMethod: %s", meth->name);
// meth->clazz = target->clazz;
// 開始新舊方法結(jié)構(gòu)體內(nèi)容的替換
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環(huán)境下的實(shí)現(xiàn)
和dalvik不同的是art目錄下又分了各種android版本號(hào)的不同實(shí)現(xiàn),所以如果設(shè)備是art虛擬機(jī)環(huán)境,不同Android版本會(huì)有不同。

art_method_replace.cpp # art_setup()
只是記錄了傳入的版本號(hào),比dalvik要簡單。
extern jboolean __attribute__ ((visibility ("hidden"))) art_setup(JNIEnv* env,
int level) {
apilevel = level;
return JNI_TRUE;
}
art_method_replace.cpp # art_setFieldFlag()
修改訪問范圍的方法就需要根據(jù)版本去調(diào)用不同方法了。
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(),實(shí)現(xiàn)邏輯和dalvik是一樣的,都是通過FromReflectedField來獲取field,然后修改accessFlag來修改訪問權(quán)限,而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_);
}
除了函數(shù)名和最后一句輸出以外沒有任何區(qū)別?????然后我發(fā)現(xiàn)每個(gè)版本都是一樣的,只是函數(shù)名和輸出有區(qū)別,有點(diǎn)迷......
art_method_replace.cpp # art_replaceMethod
替換方法的這個(gè)函數(shù)同樣需要區(qū)分版本號(hào)。
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還是一樣的邏輯。至于其它版本的實(shí)現(xiàn),都是差不多的,不過每個(gè)版本替換的內(nèi)容有一點(diǎn)差異。
void replace_7_0(JNIEnv* env, jobject src, jobject dest) {
// 獲取舊新方法的結(jié)構(gòu)體
art::mirror::ArtMethod* smeth =
(art::mirror::ArtMethod*) env->FromReflectedMethod(src);
art::mirror::ArtMethod* dmeth =
(art::mirror::ArtMethod*) env->FromReflectedMethod(dest);
// 開始結(jié)構(gòu)體內(nèi)容替換
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_);
}
四、最后的超級(jí)大圖
