Java Native Interface (JNI) 實現 Java 與原生代碼(C/C++)雙向調用。標準流程:
native 方法javah 生成 C/C++ 頭文件.so/.dll/.dylib)System.loadLibrary 加載庫package com.example.jni;
public class NativeBridge {
static {
System.loadLibrary("nativebridge"); // 加載 libnativebridge.so
}
// 原生方法聲明
public native int getInt();
public native String getString();
public native int computeSum(int a, int b);
public native void callbackJava(String msg); // C++ 回調 Java
}
# 終端執行
cd project_root
javac -d build/classes src/com/example/jni/NativeBridge.java
javah -d jni/include -classpath build/classes com.example.jni.NativeBridge
生成 com_example_jni_NativeBridge.h:
/* DO NOT EDIT - machine generated */
#include
#ifndef _Included_com_example_jni_NativeBridge
#define _Included_com_example_jni_NativeBridge
#ifdef __cplusplus
extern "C" {
#endif
JNIEXPORT jint JNICALL Java_com_example_jni_NativeBridge_getInt
(JNIEnv *, jclass);
JNIEXPORT jstring JNICALL Java_com_example_jni_NativeBridge_getString
(JNIEnv *, jclass);
JNIEXPORT jint JNICALL Java_com_example_jni_NativeBridge_computeSum
(JNIEnv *, jclass, jint, jint);
JNIEXPORT void JNICALL Java_com_example_jni_NativeBridge_callbackJava
(JNIEnv *, jclass, jstring);
#ifdef __cplusplus
}
#endif
#endif
#include "com_example_jni_NativeBridge.h"
#include
JNIEXPORT jint JNICALL Java_com_example_jni_NativeBridge_getInt
(JNIEnv *env, jclass clazz) {
return 42;
}
JNIEXPORT jstring JNICALL Java_com_example_jni_NativeBridge_getString
(JNIEnv *env, jclass clazz) {
return env->NewStringUTF("Hello from C++");
}
JNIEXPORT jint JNICALL Java_com_example_jni_NativeBridge_computeSum
(JNIEnv *env, jclass clazz, jint a, jint b) {
return a + b;
}
JNIEXPORT void JNICALL Java_com_example_jni_NativeBridge_callbackJava
(JNIEnv *env, jclass clazz, jstring msg) {
// 1. 獲取 Java 類引用
jclass bridgeClass = env->FindClass("com/example/jni/NativeBridge");
// 2. 獲取靜態方法 ID:public static void onNativeCallback(String)
jmethodID mid = env->GetStaticMethodID(bridgeClass, "onNativeCallback", "(Ljava/lang/String;)V");
// 3. 調用靜態方法
env->CallStaticVoidMethod(bridgeClass, mid, msg);
// 4. 釋放局部引用
env->DeleteLocalRef(bridgeClass);
}
# Android.mk
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := nativebridge
LOCAL_SRC_FILES := NativeBridge.cpp
include $(BUILD_SHARED_LIBRARY)
# CMakeLists.txt (現代 Android 推薦)
cmake_minimum_required(VERSION 3.10.2)
project("nativebridge")
add_library(nativebridge SHARED NativeBridge.cpp)
find_library(log-lib log)
target_link_libraries(nativebridge ${log-lib})
核心 API:FindClass、GetMethodID/GetStaticMethodID、Call*Method、NewObject、NewStringUTF、GetStringUTFChars、ReleaseStringUTFChars、DeleteLocalRef。
// C 風格(需顯式傳遞 JNIEnv*)
jclass cls = (*env)->FindClass(env, "java/lang/String");
// C++ 風格(隱式 this 指針)
jclass cls = env->FindClass("java/lang/String");
C++ 開發請始終使用 env->Method() 風格,代碼更簡潔、類型安全。
# 終端執行
javap -s -classpath build/classes com.example.jni.NativeBridge
# 輸出示例:
# public native int computeSum(int, int);
# descriptor: (II)I
# public native void callbackJava(java.lang.String);
# descriptor: (Ljava/lang/String;)V
簽名規則:B=byte, C=char, D=double, F=float, I=int, J=long, S=short, Z=boolean, L全限定類名;, [=數組。
| Java 類型 | JNI 類型 | C/C++ 類型 | 位寬 |
|---|---|---|---|
| boolean | jboolean | unsigned char | 8 |
| byte | jbyte | signed char | 8 |
| char | jchar | unsigned short | 16 |
| short | jshort | short | 16 |
| int | jint | int | 32 |
| long | jlong | long long | 64 |
| float | jfloat | float | 32 |
| double | jdouble | double | 64 |
| Object | jobject | void* | - |
| String | jstring | jobject | - |
| Class | jclass | jobject | - |
| Throwable | jthrowable | jobject | - |
| int[] | jintArray | jobject | - |
// Java -> C++: 獲取 UTF-8 指針,用完必須 Release
const char* nativeStr = env->GetStringUTFChars(javaStr, nullptr);
// 使用 nativeStr...
env->ReleaseStringUTFChars(javaStr, nativeStr);
// C++ -> Java: 創建新 String 對象
jstring javaStr = env->NewStringUTF("UTF-8 字符串");
// 注意:GetStringUTFChars 返回的是 Modified UTF-8,不支持補充平面字符(Emoji等)
// 需要完整 Unicode 時使用 GetStringChars (返回 jchar* UTF-16)
NewObject/FindClass/GetStringUTFChars 返回,生命周期綁定當前 native 方法調用,超出作用域自動釋放。手動 DeleteLocalRef 釋放大對象防止引用表溢出(默認 512)。NewGlobalRef 跨調用持久化,必須 DeleteGlobalRef 釋放,否則內存洩漏。NewWeakGlobalRef,不阻止 GC,使用前檢查 IsSameObject。jmethodID mid = env->GetMethodID(cls, "risky", "()V");
if (mid == nullptr) return; // 異常已拋出(NoSuchMethodError)
env->CallVoidMethod(obj, mid);
if (env->ExceptionCheck()) {
env->ExceptionDescribe(); // 打印堆棧
env->ExceptionClear(); // 清除異常,繼續執行或轉換為 C++ 異常
// 可選:拋出 C++ 異常或返回錯誤碼
}
FindClass/GetMethodID 較慢,靜態緩存復用。GetIntArrayElements/ReleaseIntArrayElements 替代逐元素 Get/Set。NewDirectByteBuffer 共享 Java 堆外內存,零拷貝。GetPrimitiveArrayCritical/ReleasePrimitiveArrayCritical 禁止 GC 移動數組,極短時間內使用。| 現象 | 原因 | 定位 |
|---|---|---|
| UnsatisfiedLinkError | 庫未加載、方法簽名不匹配、架構不匹配(arm64 vs x86_64) | logcat、nm -D lib.so 檢查導出符號 |
| JNI ERROR: local reference table overflow | 循環中大量創建局部引用未釋放 | 加 DeleteLocalRef、或用 Push/PopLocalFrame |
| SIGSEGV / EXC_BAD_ACCESS | 野指針、釋放後使用、數組越界 | AddressSanitizer、ndk-stack 解析 tombstone |
| ExceptionInInitializerError | 靜態塊中 loadLibrary 失敗 | 檢查庫路徑、依賴庫、架構 |
JNI 是 Java 與 C/C++ 橋樑,核心在於「簽名匹配、引用管理、異常傳遞、性能調優」。掌握 javah/javap -s 工作流、C++ 風格 API、局部/全局引用規則、字符串與數組傳遞細節,能構建高性能、穩定的混合應用。新項目優先評估 JNA 或 Panama 降低複雜度。