首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> C >> 關於C

Android NDK 開發實戰:JNI 完整流程、資料傳遞、錯誤排查與 C 反呼叫 Java(下)

     編輯:關於C

NDK 開發標準流程

  1. 建立 Android 專案,宣告 native 方法。
  2. 用 javac 編譯 Java,再用 javah 產生 .h 標頭檔(含 JNI 函數簽名)。
  3. 建立 jni/ 目錄,撰寫 .c/.cpp 實現對應函數。
  4. 編寫 Android.mk(或 CMakeLists.txt)描述建構腳本。
  5. 執行 ndk-build 產出 libs/<abi>/libxxx.so。
  6. Java 靜態區塊 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 ↔ Native 資料類型對應表

JavaJNI 型別C/C++備註
booleanjbooleanunsigned char
bytejbytesigned char
charjcharunsigned shortUTF-16
shortjshortshort
intjintint
longjlonglong long
floatjfloatfloat
doublejdoubledouble
Stringjstring_jstring*需轉換
int[]jintArray_jarray*需 Get/Release
Objectjobject_jobject*

實戰一:基本類型與字串雙向傳遞

Java 端

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);
}

C 端工具函數:jstring ↔ char*

// 將 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;
}

C 端實現

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;
}

實戰二:Native 反呼叫 Java 方法

步驟: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 fingerprintC 邏輯錯:空指標、陣列越界、雙重 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 foundC++ 共享執行期未打包android { defaultConfig { externalNativeBuild { cmake { arguments "-DANDROID_STL=c++_shared" } } } }

C 端除錯利器: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。

靜態庫 vs 動態庫選型

  • 動態庫 .so:主流選擇,體積小、支援動態載入、多模組共用。
  • 靜態庫 .a:提供給第三方整合、無依賴衝突、最終連結進單一 .so 時使用。

總結

NDK 開發核心在於簽名精確對應、記憶體管理規範(Get/Release 配對、NewGlobalRef 管理)、異常檢查。掌握 javah/javap 工作流、logcat 除錯、ndk-stack 定位崩潰,即可高效開發高效能原生模組。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved