基本數(shù)據(jù)類型
| Java Type | Native Type |
|---|---|
| boolean | jboolean |
| byte | jbyte |
| char | jchar |
| short | jshort |
| int | jint |
| long | jlong |
| float | jfloat |
| double | jdouble |
引用數(shù)據(jù)類型
| Java Type | Native Type |
|---|---|
| Object | jobject |
| Class | jclass |
| String | jstring |
| Object[] | jobjectArray |
| boolean[] | jbooleanArray |
| byte[] | jbyteArray |
| char[] | jcharArray |
| short[] | jshortArray |
| int[] | jintArray |
| long[] | jlongArray |
| float[] | jfloatArray |
| double[] | jdoubleArray |
| void | void |
JNI基本描述符
| Java Type | Filed Descriptor |
|---|---|
| boolean | Z |
| byte | B |
| char | C |
| short | S |
| int | I |
| long | J |
| float | F |
| bouble | D |
JNI引用類型描述符
一般引用類型描述符的規(guī)則如下,注意不要丟掉“;”
L + 類描述符 + ;
如,String 類型的域描述符為:
Ljava/lang/String;
數(shù)組的域描述符特殊一點(diǎn),如下,其中有多少級數(shù)組就有多少個“[”,數(shù)組的類型為類時,則有分號,為基本類型時沒有分號
[ + 其類型的域描述符
例如:
int[] 描述符為 [I
double[] 描述符為 [D
String[] 描述符為 [Ljava/lang/String;
Object[] 描述符為 [Ljava/lang/Object;
int[][] 描述符為 [[I
double[][] 描述符為 [[D
JNI函數(shù)簽名
函數(shù)簽名需要將所有參數(shù)類型的域描述符按照聲明順序放入括號,然后再加上返回值類型的域描述符,其中沒有參數(shù)時,不需要括號,如下規(guī)則:
(參數(shù)……)返回類型
例如:
String getString() ——> Ljava/lang/String;
int sum(int a, int b) ——> (II)I
void main(String[] args) ——> ([Ljava/lang/String;)V
JNI內(nèi)存管理
在c++中new的對象,如果不返回java,必須用release掉,否則內(nèi)存泄露。包括NewStringUTF,NewObject。如果返回java不必release,java會自己回收。
1.FindClass
jclass ref= (env)->FindClass("java/lang/String");
env->DeleteLocalRef(ref);
2.NewString / NewStringUTF / NewObject / NewByteArray
jstring (*NewString)(JNIEnv*, const jchar*, jsize);
const jchar* (*GetStringChars)(JNIEnv*, jstring, jboolean*);
void (*ReleaseStringChars)(JNIEnv*, jstring, const jchar*);
jstring (*NewStringUTF)(JNIEnv*, const char*);
const char* (*GetStringUTFChars)(JNIEnv*, jstring, jboolean*);
void (*ReleaseStringUTFChars)(JNIEnv*, jstring, const char*);
env->DeleteLocalRef(ref);
3.GetObjectField/GetObjectClass/GetObjectArrayElement
jclass ref = env->GetObjectClass(robj);
env->DeleteLocalRef(ref);
4.GetByteArrayElements和GetStringUTFChars
jbyte* array= (*env)->GetByteArrayElements(env,jarray,&isCopy);
(*env)->ReleaseByteArrayElements(env,jarray,array,0);
const char* input =(*env)->GetStringUTFChars(env,jinput, &isCopy);
(*env)->ReleaseStringUTFChars(env,jinput,input);
5.NewGlobalRef/DeleteGlobalRef
jobject (*NewGlobalRef)(JNIEnv*, jobject);
void (*DeleteGlobalRef)(JNIEnv*, jobject);
例如,
jobject ref= env->NewGlobalRef(customObj);
env->DeleteGlobalRef(customObj);
6.JVM:AttachCurrentThread/ DetachCurrentThread
#include <jni.h>
#include <stdio.h>
#include <malloc.h>
#include "logUtils.h"
static JavaVM *jvm=NULL;
static jobject jobj_callback=NULL;
static jmethodID mid=NULL;
static int flag=-1;
static pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t cond=PTHREAD_COND_INITIALIZER;
//在加載庫時執(zhí)行;
jint JNI_OnLoad(JavaVM *vm,void *reserved){
jvm=vm;
LOGE("------- JNI_OnLoad -------");
return JNI_VERSION_1_6;
}
//在卸載庫時執(zhí)行
void JNI_OnUnload(JavaVM *vm,void *reserved){
jvm=NULL;
LOGE("------- JNI_OnUnload -------");
}
//pthread中執(zhí)行的函數(shù)
static void *nativeWork(void*args){
LOGE(" ==================== pre nativeWork ==================== ");
JNIEnv *env=NULL;
//附加到當(dāng)前線程從JVM中取出JNIEnv, C/C++從子線程中直接回到Java里的方法時 必須經(jīng)過這個步驟
if( (*jvm)->AttachCurrentThread(jvm,&env,NULL)==0 ){
while (flag==0){
if( jobj_callback==NULL ){
//進(jìn)入等待
pthread_cond_wait(&cond,&mutex);
} else{
//回調(diào)java中的線程
(*env)->CallVoidMethod(env,jobj_callback,mid);
(*env)->DeleteGlobalRef(env,jobj_callback);
jobj_callback=NULL;
}
}
//完畢-脫離當(dāng)前線程
(*jvm)->DetachCurrentThread(jvm);
}
LOGE(" ==================== end nativeWork ==================== ");
return (void*)1;
}
JNI異常處理
java代碼調(diào)用native方法,間接調(diào)用exceptionCallback()方法,引發(fā)除零異常
public static native void doit();
public static void exceptionCallback() {
int a = 20 / 0;
System.out.println("--->" + a);
}
public static void normalCallback() {
System.out.println("In Java: invoke normalCallback.");
}
c++代碼:
void jni_doit(JNIEnv *env, jclass cls) {
jthrowable exc = NULL;
jmethodID mid = (env)->GetStaticMethodID(cls,"exceptionCallback","()V");
if (mid != NULL) {
(env)->CallStaticVoidMethod(cls,mid);
}
// 檢查JNI調(diào)用是否有引發(fā)異常
if ((env)->ExceptionCheck()) {
(env)->ExceptionDescribe();
// 清除引發(fā)的異常,在Java層不會打印異常的堆棧信息
(env)->ExceptionClear();
(env)->ThrowNew((env)->FindClass("java/lang/Exception"),"JNI拋出的異常!");
}
mid = (env)->GetStaticMethodID(cls,"normalCallback","()V");
if (mid != NULL) {
(env)->CallStaticVoidMethod(cls,mid);
}
}
在上面的例子中,我們調(diào)用了JNI的ExceptionCheck函數(shù)檢查最近一次JNi調(diào)用是否發(fā)生了異常,如果有異常這個函數(shù)返回JNI_TRUE,否則返回JNI_FALSE。當(dāng)檢測到異常時,我們調(diào)用ExceptionDescribe函數(shù)打印這個異常的堆棧信息,然后再調(diào)用ExceptionClear函數(shù)清除異常堆棧信息的緩沖區(qū)(如果不清除,后面調(diào)用ThrowNew拋出的異常堆棧信息會覆蓋前面的異常信息),最后調(diào)用ThrowNew函數(shù)手動拋出一個java.lang.Exception異常。但在JNI中拋出未捕獲的異常與Java的異常處理機(jī)制不一樣,在JNI中并不會立即終止本地方法的執(zhí)行,而是繼續(xù)執(zhí)行后面的代碼。這種情況需要我們手動來處理。如果不用return馬上退出方法的話,ThrowNew后面的代碼依然會繼續(xù)執(zhí)行,如程序運(yùn)行的結(jié)果一樣,仍然會回調(diào)normalCallback方法,打印出:invoke normalCallback.
??,謝謝大家的閱讀。