上一篇文章介绍了使用 @CName 注解配置 Kotlin Native 函数在符号表中的名称,以实现 JNI 静态绑定。但在实际开发中,动态注册方式更受青睐,因为它不受函数名约束、便于代码重构、函数名更美观,且调用效率更高,节省了静态绑定的查找过程。
如果你习惯用 C 语言编写动态绑定代码,那么在 Kotlin Native 中实现类似功能思路也很简单。以下是一个示例代码:
@CName("JNI_OnLoad")
fun JNI_OnLoad(vm: CPointer<JavaVMVar>, preserved: COpaquePointer): jint {
return memScoped {
val envStorage = alloc<CPointerVar<JNIEnvVar>>()
val vmValue = vm.pointed.pointed!!
val result = vmValue.GetEnv!!(vm, envStorage.ptr.reinterpret(), JNI_VERSION_1_6)
__android_log_print(ANDROID_LOG_INFO.toInt(), "Kn", "JNI_OnLoad")
if(result == JNI_OK){
val env = envStorage.pointed!!.pointed!!
val jclass = env.FindClass!!(envStorage.value, "com/example/hellojni/HelloJni".cstr.ptr)
val jniMethod = allocArray<JNINativeMethod>(1)
jniMethod[0].fnPtr = staticCFunction(::sayHello2)
jniMethod[0].name = "sayHello2".cstr.ptr
jniMethod[0].signature = "()V".cstr.ptr
env.RegisterNatives!!(envStorage.value, jclass, jniMethod, 1)
__android_log_print(ANDROID_LOG_INFO.toInt(), "Kn", "register say hello2, %d, %d", sizeOf<CPointerVar<JNINativeMethod>>(), sizeOf<JNINativeMethod>())
}
JNI_VERSION_1_6
}
}
实现动态注册的核心思路是通过 @CName 注解定义 JNI_OnLoad 函数,作为 Java 虚拟机加载 so 库时的入口,然后调用 JNI 的 C 接口。在 Kotlin Native 中,使用 memScope 来管理 C 变量的内存,因为 Kotlin Native 有自己的内存管理机制,而 C 语言需要手动管理。
在获取 JNIEnv 指针时,首先构造一个指针的左值类型 CPointerVar<JNIEnvVar>>。在 C 指针类型映射到 Kotlin Native 时,CPointer 的左值类型会映射为 CPointerVar,具体细节未来再系统介绍。接下来,通过 vm.pointed.pointed!! 获取 JNIInvokeInterface 结构体,因为 C 中的 JavaVM 本身是指针类型。然后使用其函数指针获取 JNIEnv。
需要注意的是,从 C 映射过来的类型无法保证空安全,因此代码中频繁使用 !! 操作符,这对开发体验不太友好。理想情况下,应该用 Kotlin Native 封装 C 接口,以 Kotlin 风格进行转换,官方 AndroidNativeActivity 示例中的 JniBridge 类就做了类似工作,但尚不完整。
动态绑定的实现部分包括查找 Java 类、分配 JNINativeMethod 数组,并将 Kotlin 函数转换为 C 函数指针。例如,使用 staticCFunction(::sayHello2) 将 Kotlin 函数转换为指针,并设置名称和签名,最后调用 RegisterNatives 进行注册。
然而,映射过来的接口函数签名非常复杂,例如 RegisterNatives 的签名,它接受 JNIEnv 值、jclass、JNINativeMethod 数组及其长度作为参数,但查看源码时会看到冗长的类型定义,容易让人困惑。这反映了 Kotlin Native 当前的一个挑战:开发体验有待提高。尽管 1.0-Beta 版本有所改进,但开发体验仍需优化,这可能影响开发者采用。
总之,本文作为对上一篇文章的补充,详细介绍了在 Kotlin Native 中实现 JNI 动态注册的方法,涵盖了代码示例和关键注意事项。