1. 基本类型:无缝直通
Java 的基本类型(int
、boolean
、float
等)在 JNI 中有直接的对应类型(jint
、jboolean
、jfloat
等)。它们在内存中的表示相同,因此传递时直接拷贝值本身,无需特殊转换。
1.1 类型映射表
Java 类型 | JNI 类型 | C/C++ 类型 | 大小/范围 |
---|---|---|---|
boolean | jboolean | unsigned char | 8位,JNI_TRUE (1)/JNI_FALSE (0) |
byte | jbyte | signed char | 8位,-128~127 |
char | jchar | unsigned short | 16位,Unicode 字符 (UTF-16) |
short | jshort | short | 16位,-32768~32767 |
int | jint | int | 32位,约±21亿 |
long | jlong | long long | 64位 |
float | jfloat | float | 32位 IEEE 754 |
double | jdouble | double | 64位 IEEE 754 |
void | void | void | - |
1.2 使用示例
Java 侧:
public native int add(int a, int b);
C++ 侧:
extern "C" JNIEXPORT jint JNICALL
Java_com_example_MyClass_add(JNIEnv *env, jobject thiz, jint a, jint b) {
// 直接像使用普通 int 一样操作 jint
return a + b;
}
2. 引用类型:需要"翻译官"
Java 的引用类型(String
、数组、任意对象)在 JNI 中对应 jstring
、jarray
/jxxxArray
、jobject
。它们不是直接可用的 C/C++ 类型,必须通过 JNIEnv*
提供的函数操作。
2.1 字符串 (jstring
):最常用的引用类型
2.1.1 Java String → C/C++ char*
(UTF-8)
// 1. 获取 UTF-8 编码的 C 字符串
const char *c_str = env->GetStringUTFChars(javaString, nullptr);
if (c_str == nullptr) return; // 必须检查!
// 2. 使用字符串
printf("C String: %s\n", c_str);
// 3. 必须释放!避免内存泄漏
env->ReleaseStringUTFChars(javaString, c_str);
2.1.2 C/C++ char*
→ Java String
const char *utf8_str = "Hello from C++!";
jstring javaString = env->NewStringUTF(utf8_str);
return javaString;
2.2 数组:处理批量数据
2.2.1 基本类型数组 (如 jintArray
)
模式 1:获取数组指针(适合大数组操作)
jint *c_array = env->GetIntArrayElements(javaArray, nullptr);
jsize length = env->GetArrayLength(javaArray);
// 操作数组元素
for (int i = 0; i < length; i++) {
c_array[i] *= 2;
}
// 释放并提交更改
env->ReleaseIntArrayElements(javaArray, c_array, 0);
模式 2:复制数组区域(适合小数组)
jsize length = env->GetArrayLength(javaArray);
jint buffer[length];
// 复制数据到缓冲区
env->GetIntArrayRegion(javaArray, 0, length, buffer);
// 操作数据...
// 将修改写回 Java 数组
env->SetIntArrayRegion(javaArray, 0, length, buffer);
2.3 对象操作:访问字段与方法
// 获取类引用
jclass clazz = env->GetObjectClass(jobject);
// 获取字段 ID(签名是关键!)
jfieldID fieldId = env->GetFieldID(clazz, "count", "I");
// 读写字段值
jint count = env->GetIntField(jobject, fieldId);
env->SetIntField(jobject, fieldId, count + 1);
// 获取方法 ID
jmethodID methodId = env->GetMethodID(clazz, "update", "(I)V");
// 调用方法
env->CallVoidMethod(jobject, methodId, 42);
3. 类型签名:JNI 的"密码本"
在获取字段/方法 ID 时,类型签名是正确识别目标的关键:
类型 | 签名表示 | 示例 |
---|---|---|
int | I | int count → I |
String | Ljava/lang/String; | String name → Ljava/lang/String; |
int[] | [I | int[] data → [I |
方法 | (参数)返回值 | void update(int) → (I)V |
获取签名的实用方法:
- 终端命令:
javap -s -p MyClass.class
- Android Studio 插件:JNI Helper
- 运行时生成:
env->GetMethodID(clazz, "method", "签名")
4. 内存管理:JNI 的雷区
4.1 引用类型管理
引用类型 | 创建方式 | 生命周期 | 释放方式 | 使用场景 |
---|---|---|---|---|
局部引用 | 大部分 JNI 函数返回 | 当前 native 方法执行期间 | DeleteLocalRef() 或自动释放 | 临时使用 |
全局引用 | NewGlobalRef() | 显式释放前 | DeleteGlobalRef() | 长期缓存 |
弱全局引用 | NewWeakGlobalRef() | 不阻止 GC 回收 | DeleteWeakGlobalRef() | 缓存可能回收的对象 |
4.2 局部引用管理最佳实践
危险代码(会导致局部引用表溢出):
for (int i = 0; i < 1000; i++) {
jstring str = env->NewStringUTF("Hello");
// 没有释放局部引用!
}
修复方案 1:显式删除
for (int i = 0; i < 1000; i++) {
jstring str = env->NewStringUTF("Hello");
// 使用字符串...
env->DeleteLocalRef(str); // 及时释放
}
修复方案 2:使用局部帧(推荐)
for (int i = 0; i < 1000; i++) {
env->PushLocalFrame(10); // 创建新的局部引用帧
jstring str = env->NewStringUTF("Hello");
// 创建其他局部引用...
env->PopLocalFrame(nullptr); // 自动释放当前帧所有局部引用
}
4.3 全局引用使用示例
// 全局缓存类引用
static jclass myGlobalClass = nullptr;
JNIEXPORT void JNICALL
Java_com_example_MyClass_init(JNIEnv *env, jobject thiz) {
if (myGlobalClass == nullptr) {
jclass localClass = env->FindClass("com/example/MyClass");
myGlobalClass = (jclass)env->NewGlobalRef(localClass);
env->DeleteLocalRef(localClass);
}
}
// 使用全局引用
env->GetStaticMethodID(myGlobalClass, "staticMethod", "()V");
// 在适当位置释放
env->DeleteGlobalRef(myGlobalClass);
5. 资源释放清单
资源类型 | 释放方式 |
---|---|
GetStringUTFChars 返回的指针 | ReleaseStringUTFChars |
数组元素指针 | Release<Type>ArrayElements |
局部引用 (大对象/循环内) | DeleteLocalRef |
全局引用 | DeleteGlobalRef |
弱全局引用 | DeleteWeakGlobalRef |
malloc /new 分配的内存 | free /delete |
黄金法则: 谁分配,谁释放! 对 JNI 函数返回的资源,必须成对调用对应的释放函数。
6. 实战:类型转换综合示例
Java 对象:
public class User {
public String name;
public int age;
public int[] scores;
public native void processInNative();
}
JNI 实现:
extern "C" JNIEXPORT void JNICALL
Java_com_example_User_processInNative(JNIEnv *env, jobject user) {
// 获取类引用
jclass userClass = env->GetObjectClass(user);
// 获取字段 ID
jfieldID nameField = env->GetFieldID(userClass, "name", "Ljava/lang/String;");
jfieldID ageField = env->GetFieldID(userClass, "age", "I");
jfieldID scoresField = env->GetFieldID(userClass, "scores", "[I");
// 获取字段值
jstring jname = (jstring)env->GetObjectField(user, nameField);
jint age = env->GetIntField(user, ageField);
jintArray jscores = (jintArray)env->GetObjectField(user, scoresField);
// 处理字符串
const char *cname = env->GetStringUTFChars(jname, nullptr);
printf("User: %s, Age: %d\n", cname, age);
env->ReleaseStringUTFChars(jname, cname);
// 处理数组
jint *scores = env->GetIntArrayElements(jscores, nullptr);
jsize length = env->GetArrayLength(jscores);
int sum = 0;
for (int i = 0; i < length; i++) {
sum += scores[i];
}
printf("Average score: %.2f\n", (float)sum / length);
// 修改数组内容
for (int i = 0; i < length; i++) {
scores[i] += 5; // 给每人加5分
}
// 释放数组资源并提交更改
env->ReleaseIntArrayElements(jscores, scores, 0);
// 释放局部引用
env->DeleteLocalRef(jname);
env->DeleteLocalRef(jscores);
}
7. 本章总结:安全贸易指南
-
基本类型:直接传递,高效安全
-
字符串转换:
GetStringUTFChars
/ReleaseStringUTFChars
配对使用- 优先使用 UTF-8 编码
-
数组操作:
- 大数组:
GetArrayElements
/ReleaseArrayElements
- 小数组:
GetArrayRegion
/SetArrayRegion
- 大数组:
-
对象操作:
- 先获取类引用,再获取字段/方法 ID
- 类型签名必须精确匹配
-
内存管理核心:
- 局部引用:警惕溢出,及时删除或使用局部帧
- 全局引用:长期缓存的首选
- 弱全局引用:不阻止 GC 的缓存方案
-
资源释放:
- 每个
GetXXX
调用必须有对应的ReleaseXXX
- 本地分配的内存必须本地释放
- 每个
避坑指南:
- 🔸 签名错误是
NoSuchMethodError
的罪魁祸首 - 🔸 忘记释放资源会导致内存泄漏
- 🔸 局部引用溢出是常见崩溃原因
- 🔸 修改数组后忘记使用
0
模式提交更改