Kotlin 写 JNI 基础
源:[Kotlin/Native for Android](https://kotlinlang.org/docs/ native-dynamic-libraries.html)
用 Kotlin/Native 编写 JNI 代码是传统 C/C++ JNI 的现代化替代方案。它提供了类型安全、自动内存管理和Kotlin 语言特性,同时保持与 Android NDK 的完全兼容。
为什么用 Kotlin 写 JNI
与传统 C/C++ JNI 对比
cpp
// native-lib.cpp
#include <jni.h>
#include <string>
extern "C" JNIEXPORT jstring JNICALL
Java_com_example_myapp_MainActivity_stringFromJNI(
JNIEnv* env,
jobject /* this */) {
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
// 需要手动管理 JNI 引用
}kotlin
// jni.kt
import kotlinx.cinterop.*
import platform.android.*
@CName("Java_com_example_myapp_MainActivity_stringFromJNI")
fun stringFromJNI(env: CPointer<JNIEnvVar>, thiz: jobject): jstring? {
return "Hello from Kotlin".toJString(env)
// 自动内存管理
}核心优势
| 特性 | C/C++ JNI | Kotlin/Native JNI |
|---|---|---|
| 类型安全 | 弱类型,容易出错 | 强类型,编译期检查 |
| 内存管理 | 手动 DeleteLocalRef | 自动 GC |
| 空安全 | 需手动检查 NULL | Kotlin 空安全 |
| 协程支持 | 不支持 | 原生支持 |
| 代码量 | 大量样板代码 | 简洁 |
| 开发效率 | 低 | 高 |
项目配置
Gradle 配置
kotlin
plugins {
id("com.android.application")
kotlin("android")
kotlin("multiplatform") // 添加 KMP 插件
}
kotlin {
// 配置 Android Native 目标
androidNativeArm64()
androidNativeX64()
sourceSets {
val androidNativeMain by creating {
dependsOn(commonMain.get())
}
val androidNativeArm64Main by getting {
dependsOn(androidNativeMain)
}
val androidNativeX64Main by getting {
dependsOn(androidNativeMain)
}
}
// 配置 JNI 动态库
targets.withType<org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget> {
binaries {
sharedLib {
baseName = "native-lib" // 生成 libnative-lib.so
}
}
}
}
android {
namespace = "com.example.myapp"
compileSdk = 34
defaultConfig {
applicationId = "com.example.myapp"
minSdk = 24
targetSdk = 34
}
// 指定 JNI 库路径
sourceSets {
getByName("main") {
jniLibs.srcDirs(
"build/bin/androidNativeArm64/releaseShared",
"build/bin/androidNativeX64/releaseShared"
)
}
}
}groovy
pluginManagement {
repositories {
google()
mavenCentral()
gradlePluginPortal()
}
}
dependencyResolutionManagement {
repositories {
google()
mavenCentral()
}
}项目结构
app/
├── src/
│ ├── androidNativeMain/kotlin/ # Kotlin/Native JNI 代码
│ │ └── jni.kt
│ ├── main/java/ # Android Kotlin 代码
│ │ └── MainActivity.kt
│ └── main/AndroidManifest.xml
└── build.gradle.kts@CName 注解
基础用法
@CName 用于指定 JNI 函数的导出名称:
kotlin
// src/androidNativeMain/kotlin/jni.kt
import kotlinx.cinterop.*
import platform.android.*
@OptIn(ExperimentalForeignApi::class)
@CName("Java_com_example_myapp_MainActivity_nativeAdd")
fun nativeAdd(
env: CPointer<JNIEnvVar>, // JNI 环境指针
thiz: jobject, // this 对象
a: jint, // int 参数
b: jint // int 参数
): jint {
return a + b
}命名规则
JNI 函数名遵循格式:Java_<package>_<class>_<method>
kotlin
// Kotlin 类
package com.example.myapp
class MainActivity {
external fun nativeAdd(a: Int, b: Int): Int
}
// 对应的 Kotlin/Native 函数
@CName("Java_com_example_myapp_MainActivity_nativeAdd")
fun nativeAdd(env: CPointer<JNIEnvVar>, thiz: jobject, a: jint, b: jint): jint包名中的 . 替换为 _,_ 替换为 _1:
kotlin
// 包名: com.example.my_app
// 类名: MainActivity
// 方法: getValue
@CName("Java_com_example_my_1app_MainActivity_getValue")
fun getValue(env: CPointer<JNIEnvVar>, thiz: jobject): jint基础 JNI 数据类型
基本类型映射
| Java/Kotlin 类型 | JNI 类型 | Kotlin/Native 类型 |
|---|---|---|
boolean | jboolean | jboolean (UByte) |
byte | jbyte | jbyte (Byte) |
char | jchar | jchar (UShort) |
short | jshort | jshort (Short) |
int | jint | jint (Int) |
long | jlong | jlong (Long) |
float | jfloat | jfloat (Float) |
double | jdouble | jdouble (Double) |
void | void | Unit |
kotlin
@OptIn(ExperimentalForeignApi::class)
// Java/Kotlin: fun calculate(a: Int, b: Long, ratio: Float): Double
@CName("Java_com_example_MainActivity_calculate")
fun calculate(
env: CPointer<JNIEnvVar>,
thiz: jobject,
a: jint,
b: jlong,
ratio: jfloat
): jdouble {
return (a + b) * ratio.toDouble()
}对象引用类型
| Java/Kotlin 类型 | JNI 类型 |
|---|---|
Object | jobject |
Class | jclass |
String | jstring |
Array | jarray |
Object[] | jobjectArray |
int[] | jintArray |
Throwable | jthrowable |
字符串操作
Java String → Kotlin String
kotlin
@OptIn(ExperimentalForeignApi::class)
fun jstring.toKotlinString(env: CPointer<JNIEnvVar>): String {
val cString = env.pointed.pointed!!.GetStringUTFChars!!(env, this, null)
val kotlinString = cString?.toKString() ?: ""
env.pointed.pointed!!.ReleaseStringUTFChars!!(env, this, cString)
return kotlinString
}
@CName("Java_com_example_MainActivity_processString")
fun processString(env: CPointer<JNIEnvVar>, thiz: jobject, input: jstring): jstring? {
val kotlinStr = input.toKotlinString(env)
val result = "Processed: $kotlinStr"
return result.toJString(env)
}Kotlin String → Java String
kotlin
@OptIn(ExperimentalForeignApi::class)
fun String.toJString(env: CPointer<JNIEnvVar>): jstring? {
return env.pointed.pointed!!.NewStringUTF!!(env, this.cstr.ptr)
}
@CName("Java_com_example_MainActivity_getMessage")
fun getMessage(env: CPointer<JNIEnvVar>, thiz: jobject): jstring? {
return "Hello from Kotlin/Native!".toJString(env)
}调用 Java 方法
获取方法 ID
kotlin
@OptIn(ExperimentalForeignApi::class)
@CName("Java_com_example_MainActivity_callJavaMethod")
fun callJavaMethod(env: CPointer<JNIEnvVar>, thiz: jobject) {
val jniEnv = env.pointed.pointed!!
// 获取类
val clazz: jclass? = jniEnv.GetObjectClass!!(env, thiz)
// 获取方法 ID
val methodId: jmethodID? = jniEnv.GetMethodID!!(
env,
clazz,
"showToast", // 方法名
"(Ljava/lang/String;)V" // 方法签名
)
// 创建参数
val message = "Called from Native".toJString(env)
// 调用方法
jniEnv.CallVoidMethod!!(env, thiz, methodId, message)
}方法签名格式
| Java/Kotlin 类型 | 签名 |
|---|---|
void | V |
boolean | Z |
byte | B |
short | S |
int | I |
long | J |
float | F |
double | D |
String | Ljava/lang/String; |
Object | Ljava/lang/Object; |
int[] | [I |
String[] | [Ljava/lang/String; |
示例签名:
kotlin
// fun add(a: Int, b: Int): Int
"(II)I"
// fun process(name: String, age: Int): String
"(Ljava/lang/String;I)Ljava/lang/String;"
// fun getData(ids: IntArray): String[]
"([I)[Ljava/lang/String;"##数组操作
读取 Java 数组
kotlin
@OptIn(ExperimentalForeignApi::class)
@CName("Java_com_example_MainActivity_sumArray")
fun sumArray(env: CPointer<JNIEnvVar>, thiz: jobject, array: jintArray): jint {
val jniEnv = env.pointed.pointed!!
// 获取数组长度
val length = jniEnv.GetArrayLength!!(env, array)
// 获取数组元素
val elements = jniEnv.GetIntArrayElements!!(env, array, null)
var sum: jint = 0
for (i in 0 until length) {
sum += elements!![i]
}
// 释放数组
jniEnv.ReleaseIntArrayElements!!(env, array, elements, JNI_ABORT)
return sum
}创建 Java 数组
kotlin
@OptIn(ExperimentalForeignApi::class)
@CName("Java_com_example_MainActivity_createArray")
fun createArray(env: CPointer<JNIEnvVar>, thiz: jobject, size: jint): jintArray? {
val jniEnv = env.pointed.pointed!!
// 创建数组
val array = jniEnv.NewIntArray!!(env, size)
// 填充数据
memScoped {
val buffer = allocArray<jint>(size)
for (i in 0 until size) {
buffer[i] = i * 2
}
jniEnv.SetIntArrayRegion!!(env, array, 0, size, buffer)
}
return array
}Android 侧使用
Kotlin Activity
kotlin
// MainActivity.kt
package com.example.myapp
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
class MainActivity : AppCompatActivity() {
companion object {
init {
System.loadLibrary("native-lib") // 加载 libnative-lib.so
}
}
// 声明 native 方法
external fun nativeAdd(a: Int, b: Int): Int
external fun getMessage(): String
external fun processString(input: String): String
external fun sumArray(array: IntArray): Int
external fun createArray(size: Int): IntArray
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 调用 native 方法
val sum = nativeAdd(10, 20)
println("Sum: $sum") // 输出: Sum: 30
val message = getMessage()
println(message) // 输出: Hello from Kotlin/Native!
val processed = processString("Kotlin")
println(processed) // 输出: Processed: Kotlin
val array = intArrayOf(1, 2, 3, 4, 5)
val arraySum = sumArray(array)
println("Array sum: $arraySum") // 输出: Array sum: 15
val newArray = createArray(5)
println(newArray.contentToString()) // 输出: [0, 2, 4, 6, 8]
}
}完整示例项目
项目结构
MyKotlinJNIApp/
├── app/
│ ├── src/
│ │ ├── androidNativeMain/kotlin/
│ │ │ └── com/example/myapp/
│ │ │ └── jni.kt
│ │ └── main/
│ │ ├── java/com/example/myapp/
│ │ │ └── MainActivity.kt
│ │ └── AndroidManifest.xml
│ └── build.gradle.kts
├── build.gradle.kts
└── settings.gradle.ktsKotlin/Native JNI 实现
kotlin
// src/androidNativeMain/kotlin/com/example/myapp/jni.kt
@file:OptIn(ExperimentalForeignApi::class)
package com.example.myapp
import kotlinx.cinterop.*
import platform.android.*
// 辅助函数:String 转换
fun String.toJString(env: CPointer<JNIEnvVar>): jstring? {
return env.pointed.pointed!!.NewStringUTF!!(env, this.cstr.ptr)
}
fun jstring.toKString(env: CPointer<JNIEnvVar>): String {
val cString = env.pointed.pointed!!.GetStringUTFChars!!(env, this, null)
val result = cString?.toKString() ?: ""
env.pointed.pointed!!.ReleaseStringUTFChars!!(env, this, cString)
return result
}
// JNI 函数
@CName("Java_com_example_myapp_MathLib_add")
fun add(env: CPointer<JNIEnvVar>, thiz: jobject, a: jint, b: jint): jint {
return a + b
}
@CName("Java_com_example_myapp_MathLib_multiply")
fun multiply(env: CPointer<JNIEnvVar>, thiz: jobject, a: jlong, b: jlong): jlong {
return a * b
}
@CName("Java_com_example_myapp_StringLib_reverse")
fun reverse(env: CPointer<JNIEnvVar>, thiz: jobject, input: jstring): jstring? {
val str = input.toKString(env)
return str.reversed().toJString(env)
}
@CName("Java_com_example_myapp_ArrayLib_average")
fun average(env: CPointer<JNIEnvVar>, thiz: jobject, numbers: jintArray): jdouble {
val jniEnv = env.pointed.pointed!!
val length = jniEnv.GetArrayLength!!(env, numbers)
val elements = jniEnv.GetIntArrayElements!!(env, numbers, null)
var sum: jlong = 0
for (i in 0 until length) {
sum += elements!![i]
}
jniEnv.ReleaseIntArrayElements!!(env, numbers, elements, JNI_ABORT)
return if (length > 0) sum.toDouble() / length else 0.0
}编译与运行
编译 Native 库
bash
# 编译 ARM64 版本
./gradlew :app:linkReleaseSharedAndroidNativeArm64
# 编译 x64 版本(模拟器)
./gradlew :app:linkReleaseSharedAndroidNativeX64
# 编译所有架构
./gradlew :app:assembleRelease输出位置
编译后的 .so 文件位于:
app/build/bin/
├── androidNativeArm64/releaseShared/
│ └── libnative-lib.so
└── androidNativeX64/releaseShared/
└── libnative-lib.so调试技巧
打印日志
kotlin
@OptIn(ExperimentalForeignApi::class)
import platform.android.__android_log_print
import platform.android.ANDROID_LOG_DEBUG
fun logDebug(tag: String, message: String) {
__android_log_print(
ANDROID_LOG_DEBUG,
tag.cstr.ptr,
message.cstr.ptr
)
}
@CName("Java_com_example_MainActivity_debugFunction")
fun debugFunction(env: CPointer<JNIEnvVar>, thiz: jobject) {
logDebug("KotlinJNI", "Debug message from Kotlin/Native")
}异常处理
kotlin
@OptIn(ExperimentalForeignApi::class)
@CName("Java_com_example_MainActivity_safeDivide")
fun safeDivide(env: CPointer<JNIEnvVar>, thiz: jobject, a: jint, b: jint): jint {
if (b == 0) {
// 抛出 Java 异常
val jniEnv = env.pointed.pointed!!
val exceptionClass = jniEnv.FindClass!!(env, "java/lang/ArithmeticException")
jniEnv.ThrowNew!!(env, exceptionClass, "Division by zero")
return 0
}
return a / b
}用 Kotlin 编写 JNI 代码结合了 Kotlin 的现代语言特性和 Native 的高性能,是 Android NDK 开发的理想选择。相比传统 C/C++,它提供了更好的类型安全、内存管理和开发体验。