native 方法。javac 編譯 Java,再用 javah 產生 .h 標頭檔(含 JNI 函數簽名)。jni/ 目錄,撰寫 .c/.cpp 實現對應函數。Android.mk(或 CMakeLists.txt)描述建構腳本。ndk-build 產出 libs/<abi>/libxxx.so。System.loadLibrary("xxx") 載入,呼叫 native 方法。Android.mk 詳解LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := hello # 產出 libhello.so
LOCAL_SRC_FILES := hello.c # 來源檔
LOCAL_LDLIBS += -llog # 連結 log 庫,支援 __android_log_print
include $(BUILD_SHARED_LIBRARY) # 共享庫
# include $(BUILD_STATIC_LIBRARY) # 靜態庫 .a
CLEAR_VARS 重置除 LOCAL_PATH 外所有變數。LOCAL_LDLIBS 連結系統庫:-llog、-landroid、-lEGL、-lGLESv2 等。| Java | JNI 型別 | C/C++ | 備註 |
|---|---|---|---|
| boolean | jboolean | unsigned char | |
| byte | jbyte | signed char | |
| char | jchar | unsigned short | UTF-16 |
| short | jshort | short | |
| int | jint | int | |
| long | jlong | long long | |
| float | jfloat | float | |
| double | jdouble | double | |
| String | jstring | _jstring* | 需轉換 |
| int[] | jintArray | _jarray* | 需 Get/Release |
| Object | jobject | _jobject* |
public class DataProvider {
static { System.loadLibrary("dataprovider"); }
public native int add(int x, int y);
public native String sayHello(String s);
public native int[] addTen(int[] arr);
}
// 將 Java String 轉為 UTF-8 C 字串 (需 free)
char *jstring_to_cstr(JNIEnv *env, jstring jstr) {
jclass cls = (*env)->FindClass(env, "java/lang/String");
jmethodID mid = (*env)->GetMethodID(env, cls, "getBytes", "(Ljava/lang/String;)[B");
jstring enc = (*env)->NewStringUTF(env, "UTF-8");
jbyteArray barr = (*env)->CallObjectMethod(env, jstr, mid, enc);
jsize len = (*env)->GetArrayLength(env, barr);
jbyte *ba = (*env)->GetByteArrayElements(env, barr, JNI_FALSE);
char *rtn = NULL;
if (len > 0) {
rtn = malloc(len + 1);
memcpy(rtn, ba, len);
rtn[len] = '\0';
}
(*env)->ReleaseByteArrayElements(env, barr, ba, JNI_ABORT);
return rtn;
}
JNIEXPORT jint JNICALL Java_com_example_DataProvider_add
(JNIEnv *env, jobject obj, jint x, jint y) {
return x + y;
}
JNIEXPORT jstring JNICALL Java_com_example_DataProvider_sayHello
(JNIEnv *env, jobject obj, jstring jstr) {
char *cstr = jstring_to_cstr(env, jstr);
char buf[256];
snprintf(buf, sizeof(buf), "%s - Hello from C!", cstr);
free(cstr);
return (*env)->NewStringUTF(env, buf);
}
JNIEXPORT jintArray JNICALL Java_com_example_DataProvider_addTen
(JNIEnv *env, jobject obj, jintArray jarr) {
jsize len = (*env)->GetArrayLength(env, jarr);
jint *carr = (*env)->GetIntArrayElements(env, jarr, 0);
for (jsize i = 0; i < len; i++) carr[i] += 10;
(*env)->ReleaseIntArrayElements(env, jarr, carr, 0); // 0=copy back & free
return jarr;
}
步驟:FindClass → GetMethodID → Call<Type>Method。
// Java 端
public void onNativeEvent(String msg) { Log.d("JNI", msg); }
// C 端
JNIEXPORT void JNICALL Java_com_example_MainActivity_triggerCallback
(JNIEnv *env, jobject obj) {
jclass cls = (*env)->GetObjectClass(env, obj); // 或 FindClass
jmethodID mid = (*env)->GetMethodID(env, cls, "onNativeEvent", "(Ljava/lang/String;)V");
jstring jmsg = (*env)->NewStringUTF(env, "Event from C");
(*env)->CallVoidMethod(env, obj, mid, jmsg);
// 檢查異常
if ((*env)->ExceptionCheck(env)) {
(*env)->ExceptionDescribe(env);
(*env)->ExceptionClear(env);
}
}
jmethodID smid = (*env)->GetStaticMethodID(env, cls, "staticFunc", "()V");
(*env)->CallStaticVoidMethod(env, cls, smid);
javap 使用
javap -s -p com.example.DataProvider
# 輸出範例:
# add(II)I
# sayHello(Ljava/lang/String;)Ljava/lang/String;
# addTen([I)[I
簽名規則:B=byte, C=char, D=double, F=float, I=int, J=long, S=short, Z=boolean, V=void, L全類名;, [陣列。
| 錯誤現象 | 原因 | 解決 |
|---|---|---|
UnsatisfiedLinkError: 方法名 | JNI 函數名不匹配(包名、類名、方法名、簽名任一不同) | 用 javah 產生標頭檔,嚴格對照 |
UnsatisfiedLinkError: Library xxx not found | .so 未打包進 APK 或架構不符 | 檢查 jniLibs/abi/、abiFilters、ndk.abiFilters |
崩潰 SIGSEGV / Build fingerprint | C 邏輯錯:空指標、陣列越界、雙重 free、棧溢位 | 加 __android_log_print 定位行號、AddressSanitizer、ndk-stack |
| 中文亂碼 | 編碼不一致 | NDK r6+ 統一 UTF-8;舊版需 new String(bytes, "ISO-8859-1") 再 getBytes("UTF-8") |
java.lang.UnsatisfiedLinkError: dlopen failed: library "libc++_shared.so" not found | C++ 共享執行期未打包 | android { defaultConfig { externalNativeBuild { cmake { arguments "-DANDROID_STL=c++_shared" } } } } |
logcat 輸出#include
#define LOG_TAG "MyJNI"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
// 使用
LOGD("x=%d, y=%d", x, y);
LOGE("malloc failed: %s", strerror(errno));
Android.mk 加入 LOCAL_LDLIBS += -llog。
.so:主流選擇,體積小、支援動態載入、多模組共用。.a:提供給第三方整合、無依賴衝突、最終連結進單一 .so 時使用。NDK 開發核心在於簽名精確對應、記憶體管理規範(Get/Release 配對、NewGlobalRef 管理)、異常檢查。掌握 javah/javap 工作流、logcat 除錯、ndk-stack 定位崩潰,即可高效開發高效能原生模組。