在項(xiàng)目開發(fā)中,時常會用到 JNI 庫,以提供一些特定的功能,而在 xposed 開發(fā)中,也會有這樣的需求,然而,在 xposed 的條件下,要加載一個 so 可不是一件容易的事。
首先的問題是跨進(jìn)程,由于 xposed 程序在執(zhí)行時,xposed 模塊與主包并不在同一進(jìn)程,因此無法直接使用以下代碼對 JNI 庫進(jìn)行加載:
init {
System.loadLibrary("xpjni")
}
如果這么做,那么只會得到一個 UnsatisfiedLinkError,因?yàn)樵?xposed 進(jìn)程所可以訪問的空間內(nèi),找不到這個 so。
那么是否可以使用另一個加載方法,即 System.load 呢?如下的代碼:
init {
System.load("/data/app/com.rarnu.xpjni.demo-1/lib/arm/libxpjni.so")
}
這樣的代碼在部分手機(jī)上可以工作,但是在部分手機(jī)上依然得到了一個 UnsatisfiedLinkError,但是具體的出錯信息變了:
java.lang.UnsatisfiedLinkError: dlopen failed:
"/data/app/com.rarnu.xpjni.demo-2/lib/arm/libxpjni.so"
is 32-bit instead of 64-bit
看出錯信息,是在一個 64 位的進(jìn)程內(nèi),加載了 32 位的庫,于是在這里就會有一個時機(jī)的問題,需要先判斷進(jìn)程的位數(shù),而在 64 位的設(shè)備上,默認(rèn)的 xposed 進(jìn)程也是 64 位的。以下就有兩個解決方案:
方案一:編譯 arm64-v8a 架構(gòu)的庫,然后在加載時,加載 64 位的庫
init {
System.load("/data/app/com.rarnu.xpjni.demo-1/lib/arm64/libxpjni.so")
}
這樣就可以適應(yīng) 64 位的設(shè)備。具體的位數(shù)判斷可以反射 dalvik.system. VMRuntime 類,并且調(diào)用其中的 is64Bit 方法。
方案二:改變 JNI 庫的加載時機(jī),將初始化時的加載修改到 hook 到指定 32 位包的加載時進(jìn)行加載。
override fun handleLoadPackage(loadPackageParam: XC_LoadPackage.LoadPackageParam) {
if (loadPackageParam.packageName == "com.rarnu.xpjni.demo") {
System.load("/data/app/com.rarnu.xpjni.demo-1/lib/arm/libxpjni.so")
}
}
由于自己的程序只有 32 位的 JNI 庫,因此會加載為 32 位的應(yīng)用,在自身被加載時加載 JNI 庫,就可以順利加載到 32 位的庫了,此時即使設(shè)備是 64 位的,也可以正常加載到 32 位的庫。
在加載完成后,再做一個簡單的函數(shù)調(diào)用,或是實(shí)現(xiàn) JNI_OnLoad 即可進(jìn)行測試,最終實(shí)現(xiàn)的效果如下:
E/XposedModule: jni library path => /data/app/com.rarnu.xpjni.demo-2/lib/arm/libxpjni.so
E/XpJNI_Native: JNI_Load
E/XposedModule: jni library loaded
E/XposedModule: jni call => 300
本文中所使用的代碼工程已上傳到 github(戳),若有不清楚的地方,還是直接看代碼吧。