Skip to content

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++ JNIKotlin/Native JNI
类型安全弱类型,容易出错强类型,编译期检查
内存管理手动 DeleteLocalRef自动 GC
空安全需手动检查 NULLKotlin 空安全
协程支持不支持原生支持
代码量大量样板代码简洁
开发效率

项目配置

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 类型
booleanjbooleanjboolean (UByte)
bytejbytejbyte (Byte)
charjcharjchar (UShort)
shortjshortjshort (Short)
intjintjint (Int)
longjlongjlong (Long)
floatjfloatjfloat (Float)
doublejdoublejdouble (Double)
voidvoidUnit
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 类型
Objectjobject
Classjclass
Stringjstring
Arrayjarray
Object[]jobjectArray
int[]jintArray
Throwablejthrowable

字符串操作

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 类型签名
voidV
booleanZ
byteB
shortS
intI
longJ
floatF
doubleD
StringLjava/lang/String;
ObjectLjava/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.kts

Kotlin/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++,它提供了更好的类型安全、内存管理和开发体验。