一、基本概念
java native Interface,是一种支持java层调用Native(c/c++)层、Native层调用java层的一种跨java虚拟机的调用技术。JNI意义:
- 支撑java平台无关性。
- 避免对既有的c/c++世界的工具重复造轮子。
二、整体调用结构
三、Java层职责
1.JNI库加载
JNI加载动作必须在方法调用之前,一般会在类的static代码块中进行。
System.loadLibrary(media_jni);
系统会根据不同平台,自动扩展为完整的库名称,例如:
| 平台名称 | 库名称 |
|---|---|
| Linux | libmedia_jni.so |
| Windows | media_jni.dll |
2.Native方法声明
通过在函数前添加“native”关键字,标注本方法由JNI层来实现。
privite static native final void native_init();
四、JNI层职责
1. 注册JNI函数
1.1 静态注册
javah工具,通过包含native方法的java.class生成JNI头文件。
当java层调用对应native方法时,JVM会根据命名规则,去寻找JNI方法,若找到,则建立关联关系(保存JNI函数指针,方便后续复用);否则报错。
弊端:
- 需要编译所有声明了native函数的java类。
- javah生成的jni函数名会特别长。
- 初次调用native函数时要根据函数名来搜索对应JNI层的函数,并建立关联,这会影响效率。
1.2 动态注册
通过JNINativaMethod的数据结构,保存java层函数和JNI函数指针之间的映射关系:
typedef struct{
const char* name; //java层函数名称
const char* signature; //java层函数签名
void* fnPtr; //JNI层函数指针
}
java层调用System.loadLibrary后,会执行so中JIN_OnLoad函数,注册函数映射,就在该方法中执行(*env)->RegisterNatives(env,clazz,gMethods,numMethods)即可。
2. 操作Object和数组
2.1 JNIEnv
JNIEnv是一个与线程相关的代表JNI环境的结构体,保存了JNI的系统函数指针,用于:
- 调用java函数
- 操作jobject对象等
JNIEnv对象在java调用native,和native调用java时获取方式不一样:
- java函数转换为JNI函数后,由虚拟机传递过来
- 调用
JavaVM的AttachCurrentThread函数获取(在后台线程退出前,需要调用javaVM的DetachCurrentThread函数来释放对应资源)
2.2 JNIEnv操作jobject
通过JNIEnv提供的getID接口,获取jfieldID和jmethodID;再通过callMethod、get/set field方法进行使用
| 成员类型 | 获取ID | 使用方法 |
|---|---|---|
| Field | env->GetFieldID(jclazz,fieldName,sig) | NativeType Get<type>Field(JNIEnv,jobject,jfieldID);void Set<type>Field(JNIEnv,jobject,jfieldID,NativeType) |
| Method | env->GetFieldID(jclazz,fieldName,sig) | NativeType Call<Type>Method(JNIEnv,jobject,jmethodID,args...) |
2.3 JNIEnv操作数组
| 数组类型 | 使用方法 |
|---|---|
| int | GetArrayLength;(*env)->GetIntArrayElements;SetIntArrayRegion |
| String | GetObjectArrayElement;GetStringUTFChars |
String[]相对特别,jni没有jstringArray,而是以jobjectArray承载,后面通过单个对象调用GetStringUTFChars方法获取char*对象,最终转换为char**对象。
3. 垃圾回收
java层的对象传递到native层之后,不能直接用局部变量保存后访问,原因是可能出现java层对象已释放,出现野指针的情况,所以需要native层进行特殊的引用处理。
3.1 JNI引用类型
| 引用类型 | 回收时机 | 使用用法 | 使用场景 |
|---|---|---|---|
| Local Reference | JNI层函数返回后,可能被回收 | env-> NewLocalRef(jobject) | 使用频度最高,类比方法内的局部变量,无野指针问题;未及时回收的该对象,可能占用过多内存 |
| Global Reference | JNI层主动释放时才会回收 | env-> NewGlobalRef(jobject);env-> DeleteGlobalRef(jobject) | JNI需要保存该java层对象时使用,记得释放即可 |
| Weak Global Reference | 运行过程中随时可能被回收 | env-> NewWeakGlobalRef(jobject);env->isSameObject; | 弱依赖,需要解耦java层和native层的情况 |
4. 异常处理
JNI层发生异常,不直接中断程序执行,而是记录该异常,在JNI层返回Java层时,会抛出该异常;但发生异常后,就只能执行资源清理类的工作(释放全局引用、释放string),一旦执行其他JNIEnv的方法,会导致crash。
一般使用案例:
if((pathStr = mEv->NewStringUTF(path))==NULL) return false;
4.1 异常处理函数
| 函数名称 | 用途 |
|---|---|
| ExceptionOccured | 判断是否发生了异常 |
| ExceptionClear | 清理JNI中异常 |
| ThrowNew | 向java层抛出异常 |