基礎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 ... }
簽名描述
基礎資料型別
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.使用
GetObjectClass
、FindClass
獲取呼叫物件的類 -
2.使用
GetFieldID
獲取欄位的ID。這裡需要傳入欄位型別的簽名描述。 -
3.使用
GetIntField
、GetObjectField
等方法,獲取欄位的值。使用SetIntField
、SetObjectField
等方法,設定欄位的值。
注意:即使欄位是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.使用
GetObjectClass
、FindClass
獲取呼叫物件的類 -
2.使用
GetStaticFieldID
獲取欄位的ID。這裡需要傳入欄位型別的簽名描述。 -
3.使用
GetStaticIntField
、GetStaticObjectField
等方法,獲取欄位的值。使用SetStaticIntField
、SetStaticObjectField
等方法,設定欄位的值。
3、jni呼叫java非靜態成員方法
-
1.使用
GetObjectClass
、FindClass
獲取呼叫物件的類 -
2.使用
GetMethodID
獲取方法的ID。這裡需要傳入方法的簽名描述。 -
3.使用
CallVoidMethod
執行無返回值的方法,使用CallIntMethod
、CallBooleanMethod
等執行有返回值的方法。
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.使用
GetObjectClass
、FindClass
獲取呼叫物件的類 -
2.使用
GetStaticMethodID
獲取方法的ID。這裡需要傳入方法的簽名描述。 -
3.使用
CallStaticVoidMethod
執行無返回值的方法,使用CallStaticIntMethod
、CallStaticBooleanMethod
等執行有返回值的方法。
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
來上拋異常。
注意,ExceptionOccurred
和ExceptionClear
一般是成對出現的,類似於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出錯!");
}
}