1. 程式人生 > >基礎JNI語法和常見使用

基礎JNI語法和常見使用

基礎JNI語法

基礎型別

Java型別 native型別 描述
boolean jboolean unsigned 8 bits
byte jbyte signed 8 bits
char jchar unsigned 16 bits
short jshort signed 16 bits
int jint signed 32 bits
long jlong signed 64 bits
float jfloat 32 bits
double jdouble 64 bits
void void N/A

引用型別

JNI為不同的java物件提供了不同的引用型別,JNI引用型別如下:

在c裡面,所有JNI引用型別其實都是jobject。

Native方法引數

  • JNI介面指標是native方法的第一個引數,JNI介面指標的型別是JNIEnv。
  • 第二個引數取決於native method是否靜態方法,如果是非靜態方法,那麼第二個引數是對物件的引用,如果是靜態方法,則第二個引數是對它的class類的引用
  • 剩下的引數跟Java方法引數一一對應
extern "C" /* specify the C calling convention */
jdouble Java_pkg_Cls_f__ILjava_lang_String_2 (

     JNIEnv *env,        /* interface pointer */

     jobject obj,        /* "this" pointer */

     jint i,             /* argument #1 */

     jstring s)          /* argument #2 */
{

     const char *str = env->GetStringUTFChars(s, 0);

     ...

     env->ReleaseStringUTFChars(s, str);

     return ...

}

點選檢視JNI介面

簽名描述

基礎資料型別

Java型別 簽名描述
boolean Z
byte B
char C
short S
int I
long J
float F
double D
void

引用資料型別

(以L開頭,以;結束,中間對應的是該型別的完整路徑)

String : Ljava/lang/String;
Object : Ljava/lang/Object;
自定義型別 Area : Lcom/xuexiang/jnidemo/Area;

陣列

(在型別前面新增[,幾維陣列就在前面新增幾個[)

int [] :[I
Long[][]  : [[J
Object[][][] : [[[Ljava/lang/Object

使用命令檢視

javap -s <java類的class檔案路徑>

class檔案存在於 build->intermediates->classes下。

在這裡插入圖片描述

JNI常見用法

1、jni訪問java非靜態成員變數

  • 1.使用GetObjectClassFindClass獲取呼叫物件的類

  • 2.使用GetFieldID獲取欄位的ID。這裡需要傳入欄位型別的簽名描述。

  • 3.使用GetIntFieldGetObjectField等方法,獲取欄位的值。使用SetIntFieldSetObjectField等方法,設定欄位的值。

注意:即使欄位是private也照樣可以正常訪問。

extern "C"
JNIEXPORT void JNICALL
Java_com_xuexiang_jnidemo_JNIApi_testCallNoStaticField(JNIEnv *env, jobject instance) {
    //獲取jclass
    jclass j_class = env->GetObjectClass(instance);
    //獲取jfieldID
    jfieldID j_fid = env->GetFieldID(j_class, "noStaticField", "I");
    //獲取java成員變數int值
    jint j_int = env->GetIntField(instance, j_fid);
    LOGI("noStaticField==%d", j_int);//noStaticField==0

    //Set<Type>Field    修改noStaticKeyValue的值改為666
    env->SetIntField(instance, j_fid, 666);
}

2、jni訪問java靜態成員變數

  • 1.使用GetObjectClassFindClass獲取呼叫物件的類

  • 2.使用GetStaticFieldID獲取欄位的ID。這裡需要傳入欄位型別的簽名描述。

  • 3.使用GetStaticIntFieldGetStaticObjectField等方法,獲取欄位的值。使用SetStaticIntFieldSetStaticObjectField等方法,設定欄位的值。

3、jni呼叫java非靜態成員方法

  • 1.使用GetObjectClassFindClass獲取呼叫物件的類

  • 2.使用GetMethodID獲取方法的ID。這裡需要傳入方法的簽名描述。

  • 3.使用CallVoidMethod執行無返回值的方法,使用CallIntMethodCallBooleanMethod等執行有返回值的方法。

extern "C"
JNIEXPORT void JNICALL
Java_com_xuexiang_jnidemo_JNIApi_testCallParamMethod(JNIEnv *env, jobject instance) {
    //回撥JNIApi中的noParamMethod
    jclass clazz = env->FindClass("com/xuexiang/jnidemo/JNIApi");
    if (clazz == NULL) {
        printf("find class Error");
        return;
    }
    jmethodID id = env->GetMethodID(clazz, "paramMethod", "(I)V");
    if (id == NULL) {
        printf("find method Error");
        return;
    }
    env->CallVoidMethod(instance, id, ++number);
}

4、jni呼叫java靜態成員方法

  • 1.使用GetObjectClassFindClass獲取呼叫物件的類

  • 2.使用GetStaticMethodID獲取方法的ID。這裡需要傳入方法的簽名描述。

  • 3.使用CallStaticVoidMethod執行無返回值的方法,使用CallStaticIntMethodCallStaticBooleanMethod等執行有返回值的方法。

5、jni呼叫java構造方法

  • 1.使用FindClass獲取需要構造的類

  • 2.使用GetMethodID獲取構造方法的ID。方法名為<init>, 這裡需要傳入方法的簽名描述。

  • 3.使用NewObject執行建立物件。

extern "C"
JNIEXPORT jint JNICALL
Java_com_xuexiang_jnidemo_JNIApi_testCallConstructorMethod(JNIEnv *env, jobject instance) {
    //獲取jclass
    jclass j_class = env->FindClass("com/xuexiang/jnidemo/Area");
    //找到構造方法jmethodID   public Area(int width, int height)
    jmethodID j_constructor_methoid = env->GetMethodID(j_class, "<init>", "(II)V");
    //初始化java類構造方法  public Area(int width, int height)
    jobject j_Area_obj = env->NewObject(j_class, j_constructor_methoid, 2, 10);

    //找到getArea()  jmethodID
    jmethodID j_getArea_methoid = env->GetMethodID(j_class, "getArea", "()I");
    //呼叫java中的   public int getArea() 獲取面積
    jint j_area = env->CallIntMethod(j_Area_obj, j_getArea_methoid);
    LOGI("面積==%d", j_area);//面積==20
    return j_area;
}

6、jni引用全域性變數

  • 使用NewGlobalRef建立全域性引用,使用NewLocalRef建立區域性引用。

  • 區域性引用,通過DeleteLocalRef手動釋放物件;全域性引用,通過DeleteGlobalRef手動釋放物件。

  • 引用不主動釋放會導致記憶體洩漏。

7、jni異常處理

  • 使用ExceptionOccurred進行異常的檢測。注意,這裡只能檢測java異常。

  • 使用ExceptionClear進行異常的清除。

  • 使用ThrowNew來上拋異常。

注意,ExceptionOccurredExceptionClear一般是成對出現的,類似於java的try-catch。

//上拋java異常
void throwException(JNIEnv *env, const char *message) {
    jclass newExcCls = env->FindClass("java/lang/Exception");
    env->ThrowNew(newExcCls, message);
}

extern "C"
JNIEXPORT void JNICALL
Java_com_xuexiang_jnidemo_JNIApi_jniTryCatchException(JNIEnv *env, jobject instance) {

    //獲取jclass
    jclass j_class = env->GetObjectClass(instance);
    //獲取jfieldID
    jfieldID j_fid = env->GetFieldID(j_class, "method", "Ljava/lang/String666;");

    //檢測是否發生Java異常
    jthrowable exception = env->ExceptionOccurred();
    if (exception != NULL) {
        LOGE("jni發生異常");
        //jni清空異常資訊
        env->ExceptionClear(); //需要和ExceptionOccurred方法成對出現
        throwException(env, "native出錯!");
    }
}

相關連結