用 Kotlin Native 寫 Jni,以後寫 Android 基本上要沒有別的語言什麼事兒了的節奏
- 2020 年 2 月 20 日
- 筆記
我在之前寫過一篇文章,講如何用 Kotlin Native 編寫 Native 程式碼通過 JNI 讓 Java 調用。當時因為完全沒有注意到 CName
這個神奇的東西的存在,所以那篇文章當中還是用 C wrapper 來做的調用。
後來,我發現根本不需要這麼麻煩啊。
我們知道 JNI 如果不通過動態註冊的話,Java native 方法與 C 函數的映射關係其實就是一個固定的命名規則:
Java_包名_類名_方法名
換句話說,如果我們在 Java 中載入的 so 庫的符號表裡面有這麼一個函數,它的名字按照標準的 C 函數命名修飾方法修飾,並且修飾之前符合上面的規則,那麼 Java 的 native 方法就可以與之對應上。
那麼假如我們有下面的 Java 類:
public class HelloJni extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ... } public native String stringFromJNI(); ... }
那麼我們只要保證 so 庫當中存在一個函數名為 Java_com_example_hellojni_HelloJni_stringFromJNI
並且返回 jstring
函數就行,至於這個 so 庫是由 C 還是 C++ 還是 golang,其實無所謂——自然,Kotlin Native也不在話下。
我們可以用 CLion 創建一個 Kotlin Native 的工程,在 gradle 當中配置為 Android 的動態鏈接庫:
... kotlin { targets { fromPreset(presets.androidNativeArm32, 'HelloWorld') // ① 配置為 Android 的工程 configure([HelloWorld]) { compilations.main.outputKinds 'DYNAMIC' // ② 配置為動態鏈接庫 } } ... } ...
然後隨便創建一個文件,寫一個全局函數,並用 CName
進行標註如下:
import kotlinx.cinterop.* import platform.android.* @CName("Java_com_example_hellojni_HelloJni_stringFromJNI") fun stringFromJNI(env: CPointer<JNIEnvVar>, thiz: jobject): jstring { memScoped { return env.pointed.pointed!!.NewStringUTF!!.invoke(env, "This is from Kotlin Native!!".cstr.ptr)!! } }
我們注意到,實際上 Kotlin Native 已經幫我們把 jni.h 這個頭文件的互調用配置搞定了,因此我們可以直接導入 jstring
這樣的類型。
然後編譯得到一個 so 庫 libknlib.so(名字取決於我們的 gradle 工程名),我們可以把它放到我們的 Android 工程當中,在運行時載入它:
static { System.loadLibrary("knlib"); }
這樣運行時就可以調用 stringFromJNI
這個方法啦。
TextView tv = (TextView)findViewById(R.id.hello_textview); tv.setText(stringFromJNI());
接下來我再給大家看幾個例子:
首先,在 Kotlin Native 當中使用 Android 的日誌 Api 列印日誌:
@CName("Java_com_example_hellojni_HelloJni_sayHello") fun sayHello(){ __android_log_print(ANDROID_LOG_INFO.toInt(), "Kn", "Hello %s", "Native") }
其次,在 Kotlin Native 當中調用 Java 的方法:
@CName("Java_com_example_hellojni_HelloJni_callLoop") fun callLoop(env: CPointer<JNIEnvVar>, thiz: jobject): jstring { memScoped { val jniEnvVal = env.pointed.pointed!! val jclass = jniEnvVal.GetObjectClass!!.invoke(env, thiz) val methodId = jniEnvVal.GetMethodID!!.invoke(env, jclass, "callFromNative".cstr.ptr, "()Ljava/lang/String;".cstr.ptr) return jniEnvVal.CallObjectMethodA!!.invoke(env, thiz, methodId, null) as jstring } }
其中 callFromNative
的定義如下:
public String callFromNative(){ return "This is from Java!!"; }
由於 Kotlin Native 本身就是兼容 C 的,因此 C 能幹的自然 Kotlin Native 也可以,這樣一來我們其實可以使用 Kotlin 將 Android App 上到虛擬機下到 Native 的程式碼全部使用 Kotlin 來編寫,真是不要太強大。
本文涉及源碼參見:hello-kni ,https://github.com/enbandari/hello-kni(閱讀原文可以點擊該鏈接~)