首先感謝一下醫(yī)生,若是沒(méi)有你催命般的催稿,還真就沒(méi)有這篇了。作為催我的代價(jià),請(qǐng)客可樂(lè)是沒(méi)跑的了:)
首先,給出章魚(yú)貓地址:rarnu/ndkmapping
NDK Mapping 的主要工作就是完成 class 從 JVM 層到 JNI 層的映射。通常情況下,當(dāng)我們進(jìn)行 JNI 開(kāi)發(fā)時(shí),無(wú)可避免的要進(jìn)行類(lèi)的傳遞操作,而 JNI 提供的 API 卻讓代碼簡(jiǎn)單不起來(lái),大量的容易出錯(cuò)的體力勞動(dòng)也是這么來(lái)的。來(lái)看看以下的代碼:
DemoInc *ret = NULL;
if (env && obj) {
ret = new DemoInc();
jclass cls = env->FindClass("com/sample/DemoInc");
jmethodID m = env->GetMethodID(cls, "getId", "()I");
ret->id = env->CallIntMethod(obj, m);
}
return ret;
大家都能讀懂的吧?就是調(diào)用一下 JVM 層 DemoInc 類(lèi)的 getId 方法,卻花費(fèi)了如此多的代碼。那么再設(shè)想一下如果是操作 List,Map 或是其他復(fù)雜類(lèi)型呢?幾十行代碼都不一定做得下來(lái)。而這正是 NDK Mapping 誕生的初衷,即幫助開(kāi)發(fā)者完成類(lèi)的映射。
來(lái)個(gè)具體的實(shí)例看看效果吧,要特別說(shuō)明的是,NDK Mapping 接受的映射類(lèi)文件必須是 Kotlin 的 data class,原因很簡(jiǎn)單,一方面是因?yàn)檫@樣的 class 足夠簡(jiǎn)單,方便解析,另一方面就是我懶??纯催@樣一個(gè) class:
data class Demo(
var v1: Int, var v2: String, var v3: Context?,
var v4: IntArray?, var v5: List<String>?, var v6: Map<Int, View?>?
)
想一下用 JNI 來(lái)操作這樣的類(lèi)需要多少代碼,你是否還記得 List 的 Add 方法簽名是什么樣的?當(dāng)然現(xiàn)在你說(shuō)不記得也沒(méi)關(guān)系了,在 NDK Mapping 的幫助下,開(kāi)發(fā)者不需要記憶任何與類(lèi)操作有關(guān)的東西。
簡(jiǎn)單的看一下 NDK Mapping 的命令參數(shù),當(dāng)直接輸入 ndkmapping 命令時(shí),即可看到如下的參數(shù)提示:
ndkmapping <options> <Kotlin Class File Path>
options:
-l language (cpp, pas)
-b build option (mk, mksh)
-m max array size (must >= 0)
-o output path
-l表示目標(biāo)語(yǔ)言,目前可以生成 C++ 和 Pascal 的類(lèi)映射,-b表示生成 Makefile,-m表示數(shù)組傳參時(shí),數(shù)組的最大下標(biāo),-o表示生成的文件輸出的位置,若是沒(méi)有該目錄,則會(huì)新建一個(gè)。當(dāng)然在最后還得再跟上 Kotlin Class 的所在目錄,ndkmapping 會(huì)自動(dòng)的映射所有的 class 文件。
完整的命令如下:
$ ndkmapping -l cpp -b mksh -m 100 -o ./out/ ./kotlin/
$ cd out
$ ./build.sh
是的,你沒(méi)有看錯(cuò),生成的代碼是可以直接編譯的,并不需要再經(jīng)過(guò)任何的修改,此時(shí)在 JNI 層的代碼內(nèi),就有了一個(gè)與 JVM 層形態(tài)完全一樣的類(lèi),可以直接操作。
而最關(guān)鍵的,是生成了兩個(gè)方法:
static Demo* Demo::fromJObject(JNIEnv *env, jobject obj);
jobject Demo::toJObject(JNIEnv *env);
顧名思議也很清晰了,一個(gè)是將 jobject 所對(duì)應(yīng)的類(lèi),翻譯成 JNI 的類(lèi),而另一個(gè),是將 JNI 的類(lèi)翻譯回 jobject。有了這兩個(gè)方法,就可以實(shí)現(xiàn)映射。而在實(shí)際開(kāi)發(fā)中,基本上也只需要調(diào)用這兩個(gè)方法,其他的一切操作,都是與平臺(tái)和語(yǔ)言本身相關(guān)的了。
下面是映射關(guān)系表,參考這個(gè)表,可以知道在生成代碼時(shí)的規(guī)則。
| Kotlin | C++ | Pascal | JNI |
|---|---|---|---|
| Int | int | Integer | jint |
| Byte | unsigned char | Byte | jbyte |
| Short | short | ShortInt | jshort |
| Long | long long | Int64 | jlong |
| Float | float | Extended | jfloat |
| Double | double | Double | jdouble |
| Boolean | bool | Boolean | jboolean |
| Char | char | Char | jchar |
| String | string | String | jstring |
| List | list | FPGList | jobject |
| Map | map | FPGMap | jobject |
| Set | set | FPGList | jobject |
| Array | [array] | [array] | jobjectArray |
對(duì)于一份生成好的代碼來(lái)說(shuō),進(jìn)行驗(yàn)證是有必要的。NDK Mapping 同樣也提供了驗(yàn)證的能力。使用 ndktester 即可。
ndktester <options> <Kotlin Class File Path>
options:
-l language (java, kotlin)
-x exported JNI code language (cpp, pas)
-b build option (mk, mkshcp)
-p base package name
-c copy path
-o output path
參數(shù)基本上都與ndkmapping類(lèi)似,要額外選擇驗(yàn)證代碼的語(yǔ)言,和原始生成的代碼語(yǔ)言,另外還需要用于 JVM 驗(yàn)證的包名,如果你需要在編譯驗(yàn)證庫(kù)后復(fù)制到其他項(xiàng)目中,可以使用-c參數(shù)。
命令的樣本如下:
ndktester -l kotlin -x cpp -b mkshcp -p com.sample.ndk -c ./jniLibs/ -o ./out/ ./kotlin/
此時(shí)就會(huì)生成用于 Kotlin 驗(yàn)證的代碼,直接引入到一個(gè)項(xiàng)目即可。
保險(xiǎn)起見(jiàn),另外還提供了一份較為復(fù)雜的類(lèi)的映射樣例代碼,可以從項(xiàng)目的 README 內(nèi)找到下載地址。