­

用 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(閱讀原文可以點擊該鏈接~)