認(rèn)識(shí)AndFix

一、AndFix的簡介

在分析實(shí)現(xiàn)前,先大概了解一下AndFix,因?yàn)槭褂闷饋肀容^簡單,所以就不過多介紹了。具體可以看AndFix的Github。

但是文檔有這樣一句.....并且AndFix已經(jīng)快兩年沒有更新了。

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. 從服務(wù)端獲取生成好的apatch文件,下載到本地。
  2. 從手機(jī)中加載apatch文件,和舊的apk合并。
mPatchManager.addPatch(path);
生成apatch文件
  1. 修改old.apk,生成修復(fù)好的new.apk
  2. 使用給的工具,用以下命令生成apatch文件。
apkpatch -f <new> -t <old> -o <output> -k <keystore> -p <***> -a <alias> -e <***>

如果報(bào)錯(cuò)的話,記得檢查一下keystore天的

  1. 將apatch文件放到客戶端。

2. 熱修復(fù)的實(shí)現(xiàn)

看下面的圖,修復(fù)實(shí)際上是將修復(fù)的方法放在patch中,用這個(gè)正確的方法去替換原有存在Bug的方法,原有的方法并沒有改變、依然存在。


image

而方法替換的原理其實(shí)是,直接在native層進(jìn)行方法的結(jié)構(gòu)體信息對(duì)換,從而實(shí)現(xiàn)方法新舊替換,實(shí)現(xiàn)熱修復(fù)功能。

3. 優(yōu)缺點(diǎn)

優(yōu)點(diǎn)
  1. 使用簡單。
  2. 修復(fù)后不需要重啟,立即生效。
缺點(diǎn)
  1. 僅限于方法的替換,如果想增加類,就無能為力了。

二、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()
  1. 遍歷mPatchDir目錄下的所有文件。
  2. 刪除遍歷到的文件在apatch_opt目錄下的同名文件。
  3. 刪除該文件。
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è)方法了。

  1. 把File轉(zhuǎn)換成一個(gè)JarFile文件進(jìn)行解壓,讀取META-INF/PATCH.MF信息。
  2. 然后開始解析Jar文件中的一些字段,這些字段都是在使用apkpatch命令生成patch文件時(shí)寫入的。
  3. 判斷后將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()。

  1. 遍歷mPatchs列表中所有的Patch對(duì)象。
  2. 拿到patch對(duì)象中所有的class文件。
  3. 傳入每個(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ù)的。

  1. 創(chuàng)建文件并做判斷。
  2. 將patch文件從原目錄復(fù)制到專門放patch的目錄下。
  3. 調(diào)用addPatch(File file),解析成Patch對(duì)象,放入mPatchs集合中。
  4. 調(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)造方法中主要干了三件事:

  1. 判斷當(dāng)前環(huán)境是否支持熱修復(fù)。
  2. 初始化驗(yàn)證對(duì)象。
  3. 驗(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)境的有效性的。有以下要求:

  1. 不是阿里的YunOS。
  2. AndFix在native層設(shè)置成功。
  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. 進(jìn)行一些驗(yàn)證工作,對(duì)比修復(fù)包的簽名與應(yīng)用的簽名是否一致,如果不通過就直接返回。
  2. 用DexFile格式來加載修復(fù)包。
  3. 遍歷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()
  1. 遍歷類中所有方法。
  2. 判斷方法上有沒有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)備的,主要做了三件事:

  1. 獲取dvmDecodeIndirectRef_fnPrt指針。
  2. 獲取dvmThreadSelf_fnPtr指針。
  3. 獲取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í)大圖

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容