NDK08_JNI访问数组、引用、异常、缓存

NDK开发汇总

一 JNI访问数组

  1. 定义native方法生产.h文件
public class JniMain {

	public native void giveArray(int[] inArray);
	
	
}
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_ray_JniMain */

#ifndef _Included_com_ray_JniMain
#define _Included_com_ray_JniMain
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_ray_JniMain
 * Method:    giveArray
 * Signature: ([I)V
 */
JNIEXPORT void JNICALL Java_com_ray_JniMain_giveArray
  (JNIEnv *, jobject, jintArray);

#ifdef __cplusplus
}
#endif
#endif

  1. c中实现h定义的方法,生成dell文件
#include "stdafx.h"
#include <stdio.h>
#include "com_ray_JniMain.h"
#include <string.h>
#include <Windows.h>
//访问基本类型数据数组
JNIEXPORT void JNICALL Java_com_ray_JniMain_giveArray
(JNIEnv * env, jobject jobj, jintArray arr) {
	int compare(jint * a, jint *b);
	//jintArray->jint *
	jint *elements = (*env)->GetIntArrayElements(env, arr, NULL);
	if (elements == NULL) {
		return;
	}
	//数组长度
	int len = (*env)->GetArrayLength(env, arr);
	qsort(elements, len, sizeof(jint), compare);
	//释放可能的内存
	//将JNI  修改的数据重新写回原来的内存
	(*env)->ReleaseIntArrayElements(env, arr, elements, JNI_COMMIT);
}

int compare(jint * a, jint *b) {
	return *a - *b;
}
  1. 集成dll,调用native方法
public class JniMain {

	public native void giveArray(int[] inArray);
	
	
	static {
		System.loadLibrary("lsn8");
	}
	
	public static void main(String[] args) {
		
		JniMain jniMain = new JniMain();
		
		 int[] array = {3,9,2,50,6,13};
	        jniMain.giveArray(array);
	        for (int i = 0; i < array.length; i++) {
	        	System.out.println(array[i]);
	        }
	}
	
}

运行结果:
2
3
6
9
13
50

二 访问引用数据类型的数组

  1. native方法
	public native String[] initStringArray(int size);
  1. 实现.h声明方法
// 访问引用数据类型的数组
JNIEXPORT jobjectArray JNICALL Java_com_ray_JniMain_initStringArray
(JNIEnv * env, jobject jobj, jint size) {
	//创建jobjectArray
	jobjectArray result;
	jclass jclz;
	int 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;
	}
	// 赋值
	for (i = 0; i < size; i++) {
		//C 字符串
		char * c_str = (char *)malloc(256);
		memset(c_str, 0, 256);
		//将int 转换成为char
		sprintf(c_str, "hello num: %d\n", i);
		//C ->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);
	}

	//返回jobjectArray
	return result;
}
  1. 调用
  String[] strArr = jniMain.initStringArray(5);
  for (int i = 0; i < strArr.length; i++) {
	System.out.format("strArr[%d] = %s\n",i, strArr[i]);
  }
  1. 运行结果

strArr[0] = hello num: 0

strArr[1] = hello num: 1

strArr[2] = hello num: 2

strArr[3] = hello num: 3

strArr[4] = hello num: 4

三 JNI引用

1 局部引用

定义方式多样: FindClass,NewObject,GetObjectClass,NewCharArray… NewLocalRef()
释放方式: 1 方法调用完JVM 会自动释放 2.DeleteLocalRef
注意: 不能在多线程里面使用

JNIEXPORT void JNICALL Java_com_ray_JniMain_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");
		//创建一个Date类型的局部引用
		jobject obj = (*env)->NewObject(env, cls, jmid);
		//使用这个引用
		//释放引用
		(*env)->DeleteLocalRef(env, cls);
		(*env)->DeleteLocalRef(env, obj);
	}
}

2 全局引用

//跨线程,跨方法使用
// NewGlobalRef 是创建全局引用的唯一方法

	public native void createGlobalRef();
	public native String getglobalRef();
	public native void delGlobalRef();
jstring global_str;
JNIEXPORT void JNICALL Java_com_ray_JniMain_createGlobalRef
(JNIEnv * env, jobject jobj) {
	jobject obj = (*env)->NewStringUTF(env, "JNI is intersting");
	global_str = (*env)->NewGlobalRef(env, obj);
}


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

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

调用:

	    jniMain.createGlobalRef();
        System.out.println(jniMain.getglobalRef());
        jniMain.delGlobalRef();
	    System.out.println("globalRef is released");

3 弱全局引用

//它不会阻止GC,/跨线程,跨方法使用

jclass g_weak_cls;
JNIEXPORT jstring JNICALL Java_com_ray_JniMain_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里面程序出现异常,java调用的时候程序中断,try catch无法捕获
  • jni 通过 throwable ex = (*env)->ExceptionOccurred(env)获取异常,(*env)->ExceptionClear(env);清除异常
  • 创建异常: newExc = (*env)->FindClass(env, “java/lang/IllegalArgumentException”)
    将异常传递到java层: (*env)->ThrowNew(env, newExc, "Throw exception from JNI: GetFieldID faild ")
public class JniMain {
//public String key= "key";
}
public native void exception();
	
static {
	System.loadLibrary("lsn8");
}

public static void main(String[] args) {
	JniMain jniMain = new JniMain();
	//将key注释掉,找不到key报异常;取消注释运行异常
	    try {
	    	jniMain.exception();
		} catch (Exception e) {
			System.out.println(e.toString());
		}
}

c中实现native方法:

//JNI 异常处理
JNIEXPORT void JNICALL Java_com_ray_JniMain_exception
(JNIEnv * env, jobject jobj) {

	jclass cls = (*env)->GetObjectClass(env, jobj);
	jfieldID fid = (*env)->GetFieldID(env, cls, "key", "Ljava/lang/String;");
	//检查是否发送异常
	jthrowable ex = (*env)->ExceptionOccurred(env);
	// 判断异常是否发送
	if (ex != NULL) {
		jclass newExc;
		//清空JNI 产生的异常
		(*env)->ExceptionClear(env);
		//IllegalArgumentException
		newExc = (*env)->FindClass(env, "java/lang/IllegalArgumentException");
		if (newExc == NULL)
		{
			printf("exception\n");
			return;
		}
		(*env)->ThrowNew(env, newExc, "Throw exception from JNI: GetFieldID faild ");
	}
}

运行结果:
java.lang.IllegalArgumentException: Throw exception from JNI: GetFieldID faild

五 缓存

static jclass cls_string = NULL;

  • 方法内声明的只在方法中有效
  • 方法外的,在声明之后,本文件内有效

1 局部静态变量进行缓存


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

重复调用:

	for(int i = 0;i <100;i++) {
			jniMain.cached();
		}

只输出一次:
GetFieldID

如果声明成:jfieldID fid = NULL,不加static,会打印100次;

2 全局变量

static jfieldID fid_glb;
JNIEXPORT void JNICALL Java_com_ray_JniMain_cachedGlobal
(JNIEnv * env, jclass cls) {
	if (fid_glb == NULL) {
		fid_glb = (*env)->GetFieldID(env, cls, "key", "Ljava/lang/String;");
		printf("cachedGlobal GetFieldID\n");
	}
}
	public native static void cachedGlobal();
	public static void main(String[] args) {
		for(int i = 0;i <100;i++) {
			cachedGlobal();
		}
	}

如果去掉static,则整个文件都可以使用

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

1 定义native方法

public native String AcessCacheNewString();
	
public native String AcessCF();

2 实现native方法,利用static缓存

JNIEXPORT jstring JNICALL Java_com_ray_JniMain_AcessCacheNewString
(JNIEnv * env, jobject jobj) {
	static jclass cls_string = NULL;
	if (cls_string == NULL) {
		printf("Java_JniMain_AcessCache_newString out: \n");
		cls_string = (*env)->FindClass(env, "com/ray/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("ray out java_jnimain_acesscache_newstring\n");

	return NULL;
}


JNIEXPORT jstring JNICALL Java_com_ray_JniMain_AcessCF
(JNIEnv * env, jobject jobj) {
	return Java_com_ray_JniMain_AcessCacheNewString(env, jobj);
}

3 生产dll,只调用一次,正常

public static void main(String[] args) {
		
		JniMain jniMain = new JniMain();
		jniMain.AcessCF();
}

输出结果:
Java_JniMain_AcessCache_newString out:
ray out java_jnimain_acesscache_newstring

4 申请大内存,调用多次

public static void main(String[] args) {
		
			jniMain.AcessCF();
		
		 for(int i = 0; i < 100; i++) {
	        	jniMain.AcessCF();
	        	//大数组申请大量内存触发GC
	        	long[] ar = new long[1024*1024];
	      }
}

输出报错:
java.lang.NoSuchMethodError: getRef
Java_JniMain_AcessCache_newString out:
ray out java_jnimain_acesscache_newstring
ray out java_jnimain_acesscache_newstring
ray out java_jnimain_acesscache_newstring
ray out java_jnimain_acesscache_newstring
ray out java_jnimain_acesscache_newstring
C exceptions happend
ray out java_jnimain_acesscache_newstring
Exception in thread “main”

5 解决

  • 一般请求不要将局部引用赋值给静态的局部变量
  • 将static jclass cls_string的static去掉
  • 如果要用静态的局部变量,在使用之前判断它的有效性,抓获异常,保证程序执行

七 Demo

NDK08_JNI访问数组、引用、异常、缓存

发布了269 篇原创文章 · 获赞 123 · 访问量 16万+

猜你喜欢

转载自blog.csdn.net/baopengjian/article/details/104905127