NDK开发学习笔记(3):JNI访问数组、引用、异常处理、缓存策略

时间:2021-01-31 21:58:49
/*
* jni访问java中的数组
*/

JNIEXPORT void JNICALL Java_com_mei_test_jni_JniTest_giveArray
(JNIEnv *env, jobject jobj, jintArray arr) {

int compare(jint *a, jint *b);//声明方法,可以在函数的内部
//jintArray->jint *
jint *elemts = (*env)->GetIntArrayElements(env, arr, NULL);
if (elemts == NULL) {
return;
}

//获取数组的长度
int len = (*env)->GetArrayLength(env, arr);
qsort(elemts, len, sizeof(jint), compare);//c的库中提供的排序方法

//释放可能的内存;将JNI修改的数据重新写回原来的内存,所以如果不调用此方法的话,java中的打印的数据还是没有排序的
(*env)->ReleaseIntArrayElements(env, arr, elemts, JNI_COMMIT);
}

int compare(jint *a, jint *b) {
return *a - *b;
}


/*
* 访问引用数据类型的数组
*通过jni实现一个数组并返回给java使用
*/

JNIEXPORT jobjectArray JNICALL Java_com_mei_test_jni_JniTest_initStringArray
(JNIEnv *env, jobject jobj, jint size ) {
//1、创建jobjectArray
jobjectArray result;
jclass jclz;
jint i;
jclz = (*env)->FindClass(env, "java/lang/String");
if (jclz == NULL) {
return NULL;
}
result = (*env)->NewObjectArray(env, size, jclz, jobj);
if (result == NULL) {
return NULL;
}
//2、赋值
for ( i = 0; i < size; i++)
{
//c字符串
char * c_str = (char*)malloc(256);
memset(c_str, 0, 256);
//c语言,将int转换成为char
sprintf(c_str, "hello num:%d\n", i);
//C String->jstring
jstring str = (*env)->NewStringUTF(env, c_str);
if (str == NULL) {
return NULL;
}
//将jstring赋值给数组
(*env)->SetObjectArrayElement(env, result, i, str);
free(c_str);
c_str = NULL;
//(*env)->DeleteGlobalRef(env, str);
}
//3、返回
return result;
}

//JNI引用
//局部引用
//全局引用
//弱全局引用

/*
* 局部引用:定义方式多样
* 创建方式:有很多,如:FindClass,NewObject,GetObjectClass,NewCharArray....NewLocalRef()等等都是创建局部引用的方式。
* 产生了一个引用类型的操作或方法,都可以认为是创建了一个局部引用,所以FindClass也是一个创建局部引用的操作
* 释放局部引用的方式:1、方法调用完JVM会自动释放;2、通过DeleteLocalRef手动释放。
* 注意:局部引用不能在多线程中使用,
*/

JNIEXPORT void JNICALL Java_com_mei_test_jni_JniTest_localRef
(JNIEnv *env, jobject jobj) {
int i = 0;
for ( i = 0; i < 5; i++)
{
jclass cls = (*env)->FindClass(env, "java/util/Date");//这个也是一个局部引用
jmethodID jmid = (*env)->GetMethodID(env, cls, "<init>", "()V");
//(*env)->NewObject(env, cls, jmid)这个就是创建了一个Date类型的局部引用
jobject obj = (*env)->NewObject(env, cls, jmid);
//使用这个引用

//释放引用
/*
*在JNI中,会创建一个布局引用表,其大小为512,即能存储512个局部引用,如果我们没有及时动释放的话,
*如果在某一时间创建的局部引用超过了512个,就会造成内存溢出。因此即使JVM会自动释放内存空间,我们还是要自己手动释放,避免内存溢出
*/

(*env)->DeleteLocalRef(env, cls);
(*env)->DeleteLocalRef(env, obj);
}
}

/*
* 全部引用
* 创建方式:NewGlobalRef方法式创建全局引用的唯一方式
* 释放方式:通过DeleteGlobalRef方法释放全局引用
* 优点:可以跨线程,垮方法使用
*/

jstring global_str;
JNIEXPORT void JNICALL Java_com_mei_test_jni_JniTest_createGlobalRef
(JNIEnv *env, jobject jobj) {
jobject obj = (*env)->NewStringUTF(env, "JNI is intersting");
global_str = (*env)->NewGlobalRef(env, obj);//这就创建了一个全局引用
}

JNIEXPORT jstring JNICALL Java_com_mei_test_jni_JniTest_getGlobalRef
(JNIEnv *env, jobject jobj) {
return global_str;
}

JNIEXPORT void JNICALL Java_com_mei_test_jni_JniTest_deleteGlobalRef
(JNIEnv *env, jobject jobj) {
(*env)->DeleteGlobalRef(env, global_str);
}


/*
* 弱全局引用
* 创建方式:NewWeakGlobalRef方法式创建弱全局引用的唯一方式
* 释放方式:自动释放,无需手动释放
* 优点:弱全局引用不会阻止GC,即不会发生内存泄露,不会释放不掉,当内存不足时,优先被释放
*/

jclass g_weak_cls;
JNIEXPORT jstring JNICALL Java_com_mei_test_jni_JniTest_createWeakRef
(JNIEnv *env, jobject jobj){
jclass cls_string = (*env)->FindClass(env, "java/lang/String");
g_weak_cls = (*env)->NewWeakGlobalRef(env, cls_string);
return g_weak_cls;
}

/*
* JNI异常处理
*/

JNIEXPORT void JNICALL Java_com_mei_test_jni_JniTest_exception
(JNIEnv *env, jobject jobj) {
jclass cls = (*env)->GetObjectClass(env, jobj);
jfieldID fid = (*env)->GetFieldID(env, cls, "key", "Ljava/lang/String;");//java中没有字段key,所以会报错

jthrowable exception = (*env)->ExceptionOccurred(env);
if (exception !=NULL)//说明发生了异常
{
jclass newExc;
//发送一个异常,让java可以捕捉到
(*env)->ExceptionClear(env);//清空JNI产生的异常
//IllegalArgumentException
newExc = (*env)->FindClass(env, "java/lang/IllegalArgumentException");
if (newExc ==NULL)
{
printf("exception\n");
return;
}
//把异常抛给java层,让java可以捕获
(*env)->ThrowNew(env, newExc, "Throw exception from JNI: GetFieldID faild ");
}

printf("exception\n");

}


/*
* JNI 局部静态变量进行缓存
* static 关键字:声明的变量是一个静态变量,static关键字声明的变量,在c语言中,会存储在一个静态区中,
* 存储在静态区中的变量只需要声明和初始化一次,以后再次使用的时候,就可以直接使用,不会再从新创建,
* 使用范围:局部的静态变量只在定义它的方法内有效,其他方法无法引用。
*/

JNIEXPORT void JNICALL Java_com_mei_test_jni_JniTest_cached
(JNIEnv *env, jobject jobj) {
jclass cls = (*env)->GetObjectClass(env, jobj);
static jfieldID fid;
if (fid == NULL) {
fid = (*env)->GetFieldID(env, cls, "mKey", "Ljava/lang/String;");
printf("GetFieldID\n");
}
}


/*
* JNI 全局静态变量进行缓存
* 使用范围:在定义位置之后的所有方法都可以调用,之前的方法无法调用
*/

static jfieldID fid_glb;
JNIEXPORT void JNICALL Java_com_mei_test_jni_JniTest_cachedGlobal
(JNIEnv *env, jobject jobj){
jclass jclz = (*env)->GetObjectClass(env, jobj);
if (fid_glb == NULL) {
fid_glb = (*env)->GetFieldID(env, jclz, "mKey", "Ljava/lang/String;");
printf("fid_glb init\n");//
}
}

/*
* JNI 缓存策略和弱引用联合使用带来的问题
*
*/

JNIEXPORT jstring JNICALL Java_com_mei_test_jni_JniTest_accessCacheNewString
(JNIEnv *env, jobject jobj) {
//定义一个静态的局部变量
static jclass cls_string = NULL;//当内存不足的时候,在方法调用结束之后,JVM就会释放所有的局部变量,
//这个时候cls_string这个局部静态变量就会指向一个空的内存空间,即产生了野指针,
//解决方式:去掉static关键字,如果非要使用static,就一定要做好异常处理
if (cls_string == NULL)
{
printf("alvin in Java_JniMain_AcessCache_newString out: \n");
//给局部静态变量赋一个局部引用
cls_string = (*env)->FindClass(env, "com/mei/test/jni/Refrence");
}
//使用这个静态局部变量
jmethodID jmid = (*env)->GetMethodID(env, cls_string, "getRef", "(I)I");
jthrowable ex = (*env)->ExceptionOccurred(env);
if (ex != NULL)
{
jclass newExc;
// 让java 继续运行
(*env)->ExceptionDescribe(env);//输出关于这个异常的描述
(*env)->ExceptionClear(env);
printf("C exceptions happend\n");
}

printf("alvin out Java_JniMain_AcessCache_newString\n");
return NULL;
}


/*
* Class: com_mei_test_jni_JniTest
* Method: accessCF
* Signature: ()V
*/

JNIEXPORT jstring JNICALL Java_com_mei_test_jni_JniTest_accessCF
(JNIEnv *env, jobject jobj) {
//Sleep(100);
return Java_com_mei_test_jni_JniTest_accessCacheNewString(env, jobj);
}