1. 程式人生 > >Android NDK學習:JNI中的陣列、引用和異常的處理

Android NDK學習:JNI中的陣列、引用和異常的處理

JNI的文件

JNI陣列操作

呼叫陣列

Java 方法

    //給陣列排序
    public native void sortArray(int[] array);

C 程式碼

//類似java比較器
int fCompare(const int* a, const int* b){
    return (*a) - (*b);
}


JNIEXPORT void JNICALL Java_com_liu_JniUtils_sortArray
(JNIEnv * env, jobject obj,jintArray arr){
    //轉化陣列
    jint* int_arr = (*env)->GetIntArrayElements(env, arr, NULL
); //獲取陣列的長度 int len = (*env)->GetArrayLength(env, arr); //排序。陣列,陣列的長度,單個元素的大小,比較器 qsort(int_arr, len, sizeof(int), fCompare); //非常重要的同步。不然的話,資料沒有變化 (*env)->ReleaseIntArrayElements(env, arr, int_arr, 0); }

ReleaseIntArrayElements()函式最後一個值:
- 0。Java陣列更新,釋放C/C++陣列
- JNI_ABORT。Java陣列不更新,釋放C/C++陣列
- JNI_COMMIT。Java陣列更新,不釋放C/C++陣列。(函式執行完,陣列也會釋放)

返回一個數組

Java 程式碼

    public native int[] getDatas(int len);

C 程式碼

//返回一個數組
JNIEXPORT jintArray JNICALL Java_com_liu_JniUtils_getDatas
(JNIEnv * env, jobject obj,jint len){
    //建立一個指定大小的陣列
    jintArray array = (*env)->NewIntArray(env, len);
    int* ints = (*env)->GetIntArrayElements(env, array, NULL);

    int i = 0
; for (; i < len; i++){ ints[i] = i; } //同步,不然,沒有值 (*env)->ReleaseIntArrayElements(env, array, ints, 0); return array; }

引用的處理

引用型別有:區域性引用和全域性引用
作用:讓虛擬機器回收一個JNI變數

區域性引用

當建立了大量的區域性引用,在使用完成的時候,後面沒有執行完,需要回收。

java 程式碼

public native void useLocalRef();

C 程式碼

//區域性引用
JNIEXPORT jintArray JNICALL Java_com_liu_JniUtils_useLocalRef
(JNIEnv * env, jobject obj){
    //建立Date物件
    jclass cls = (*env)->FindClass(env,"java/util/Date");
    jmethodID mid = (*env)->GetMethodID(env, cls, "<init>", "()V");
    jobject date = (*env)->NewObject(env, cls, mid);


    //使用完了。回收物件
    (*env)->DeleteLocalRef(env, date);
}

全域性引用

它可以在多個函式共享。
然後,手動控制記憶體的釋放。

    //生成string,java列印
    public native String getGlobalRef();
    //銷燬string
    public native void useAndDelGlobalRef();

C 程式碼

jstring global_str;

//全域性引用,獲取
JNIEXPORT jstring JNICALL Java_com_liu_JniUtils_getGlobalRef
(JNIEnv * env, jobject obj){
    jstring str = (*env)->NewStringUTF(env, "this is a global str");
    global_str = (*env)->NewGlobalRef(env, str);
    return global_str;
}
//全域性引用。銷燬
JNIEXPORT void JNICALL Java_com_liu_JniUtils_useAndDelGlobalRef
(JNIEnv * env, jobject obj){


    (*env)->DeleteGlobalRef(env, global_str);
}

弱全域性引用

物件不常用時,可以使用。記憶體不足時,可以釋放該物件。

跟全域性引用建立,銷燬的方法類似

//建立
(*env)->NewWeakGlobalRef(env,str);
//銷燬
(*env)->DeleteWeakGlobalRef(env,str)

異常處理


當c/c++程式碼出現異常後,它們還是會向下走的。所以,當檢測到異常後,我們需要return;
防止下面的接著出現異常。

異常處理有:
- 自己處理
- 拋給Java層,Java捕獲,處理

自己處理異常

(*env)->ExceptionOccurred,ExceptionCheck來判斷是否發生了異常。

JNIEXPORT void JNICALL Java_com_liu_demojni2_MainActivity_stringFromJNI(
        JNIEnv *env,jobject ojb) {
    char* a = NULL;
    *a = "b";

    //自己處理異常,
    if((*env)->ExceptionCheck(env)){
        //列印資訊
        (*env)->ExceptionDescribe(env);
        //清空異常
        (*env)->ExceptionClear(env);
        //補救或者return;
        return ;
    }
    //不往下執行

把異常拋給Java層

JNIEXPORT void JNICALL Java_com_liu_demojni2_MainActivity_stringFromJNI(
        JNIEnv *env,jobject ojb) {
    ....
    //把異常拋給Java層
    if (((*env)->ExceptionOccurred(env))){
        (*env)->ExceptionDescribe(env);
        (*env)->ExceptionClear(env);
        jclass cls = (*env)->FindClass(env, "java/lang/Exception");
        (*env)->ThrowNew(env, cls, "this is a exception!");
        return;
    }
    ....
}

異常除錯


在build檔案裡面有兩個so的檔案,cmake跟transform資料夾裡面。
而且,cmake裡面的會比transform裡面的大很多。
cmake裡面的就是帶符號表的so。這個很重要,我們可以根據符號表來定位到,我們的具體錯誤位置。

通過’ndk-stack’ 簡單工具,來定位Native異常的具體位置。

adb logcat | $NDK/ndk-stack -sym $PROJECT_PATH/obj/local/armeabi
//或者從檔案讀取
 adb logcat > /tmp/foo.txt
    $NDK/ndk-stack -sym $PROJECT_PATH/obj/local/armeabi -dump foo.txt

模擬一個異常

#include <jni.h>
#include <string.h>

#include "com_liu_demojni2_MainActivity.h"

JNIEXPORT jstring JNICALL Java_com_liu_demojni2_MainActivity_stringFromJNI(
        JNIEnv *env,jobject ojb) {
    char* a = NULL;
    *a = "b";
    return (*env)->NewStringUTF(env,"from C");
}

通過命令

D:\develop\workspace\wort_studio\DemoJni2>adb logcat|ndk-stack -sym app\build\intermediates\cmake\debug\obj\x86

列印的資訊

********** Crash dump: **********
Build fingerprint: 'google/sdk_gphone_x86/generic_x86:8.1.0/OSM2.171116.002/4458339:userdebug/dev-keys'
pid: 3991, tid: 3991, name: om.liu.demojni2  >>> com.liu.demojni2 <<<
signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x0
Stack frame 04-11 23:37:15.017  4019  4019 F DEBUG   :     #00 pc 00000510  /data/app/com.liu.demojni2-8NcMhlAmcuqtiWQHojTaVg==/lib/x86/libnative-lib.so (Java_
com_liu_demojni2_MainActivity_stringFromJNI+64): Routine Java_com_liu_demojni2_MainActivity_stringFromJNI at D:\develop\workspace\wort_studio\DemoJni2\app\src\
main\cpp/native-lib.c:9
Stack frame 04-11 23:37:15.017  4019  4019 F DEBUG   :     #01 pc 00009158  /data/app/com.liu.demojni2-8NcMhlAmcuqtiWQHojTaVg==/oat/x86/base.odex (offset 0x900
0)


這裡就看到cpp/native-lib.c:9 在這個c檔案的第九行就打印出來了。