基本数据类型
Java Type | Native Type |
---|---|
boolean | jboolean |
byte | jbyte |
char | jchar |
short | jshort |
int | jint |
long | jlong |
float | jfloat |
double | jdouble |
引用数据类型
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引用类型描述符
一般引用类型描述符的规则如下,注意不要丢掉“;”
L + 类描述符 + ;
如,String 类型的域描述符为:
Ljava/lang/String;
数组的域描述符特殊一点,如下,其中有多少级数组就有多少个“[”,数组的类型为类时,则有分号,为基本类型时没有分号
[ + 其类型的域描述符
例如:
int[] 描述符为 [I
double[] 描述符为 [D
String[] 描述符为 [Ljava/lang/String;
Object[] 描述符为 [Ljava/lang/Object;
int[][] 描述符为 [[I
double[][] 描述符为 [[D
JNI函数签名
函数签名需要将所有参数类型的域描述符按照声明顺序放入括号,然后再加上返回值类型的域描述符,其中没有参数时,不需要括号,如下规则:
(参数……)返回类型
例如:
String getString() ——> Ljava/lang/String;
int sum(int a, int b) ——> (II)I
void main(String[] args) ——> ([Ljava/lang/String;)V
JNI内存管理
在c++中new的对象,如果不返回java,必须用release掉,否则内存泄露。包括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;
//在加载库时执行;
jint JNI_OnLoad(JavaVM *vm,void *reserved){
jvm=vm;
LOGE("------- JNI_OnLoad -------");
return JNI_VERSION_1_6;
}
//在卸载库时执行
void JNI_OnUnload(JavaVM *vm,void *reserved){
jvm=NULL;
LOGE("------- JNI_OnUnload -------");
}
//pthread中执行的函数
static void *nativeWork(void*args){
LOGE(" ==================== pre nativeWork ==================== ");
JNIEnv *env=NULL;
//附加到当前线程从JVM中取出JNIEnv, C/C++从子线程中直接回到Java里的方法时 必须经过这个步骤
if( (*jvm)->AttachCurrentThread(jvm,&env,NULL)==0 ){
while (flag==0){
if( jobj_callback==NULL ){
//进入等待
pthread_cond_wait(&cond,&mutex);
} else{
//回调java中的线程
(*env)->CallVoidMethod(env,jobj_callback,mid);
(*env)->DeleteGlobalRef(env,jobj_callback);
jobj_callback=NULL;
}
}
//完毕-脱离当前线程
(*jvm)->DetachCurrentThread(jvm);
}
LOGE(" ==================== end nativeWork ==================== ");
return (void*)1;
}
JNI异常处理
java代码调用native方法,间接调用exceptionCallback()方法,引发除零异常
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调用是否有引发异常
if ((env)->ExceptionCheck()) {
(env)->ExceptionDescribe();
// 清除引发的异常,在Java层不会打印异常的堆栈信息
(env)->ExceptionClear();
(env)->ThrowNew((env)->FindClass("java/lang/Exception"),"JNI抛出的异常!");
}
mid = (env)->GetStaticMethodID(cls,"normalCallback","()V");
if (mid != NULL) {
(env)->CallStaticVoidMethod(cls,mid);
}
}
在上面的例子中,我们调用了JNI的ExceptionCheck函数检查最近一次JNi调用是否发生了异常,如果有异常这个函数返回JNI_TRUE,否则返回JNI_FALSE。当检测到异常时,我们调用ExceptionDescribe函数打印这个异常的堆栈信息,然后再调用ExceptionClear函数清除异常堆栈信息的缓冲区(如果不清除,后面调用ThrowNew抛出的异常堆栈信息会覆盖前面的异常信息),最后调用ThrowNew函数手动抛出一个java.lang.Exception异常。但在JNI中抛出未捕获的异常与Java的异常处理机制不一样,在JNI中并不会立即终止本地方法的执行,而是继续执行后面的代码。这种情况需要我们手动来处理。如果不用return马上退出方法的话,ThrowNew后面的代码依然会继续执行,如程序运行的结果一样,仍然会回调normalCallback方法,打印出:invoke normalCallback.
🎉,谢谢大家的阅读。