JNI编程

129 阅读3分钟

一、基本概念

java native Interface,是一种支持java层调用Native(c/c++)层、Native层调用java层的一种跨java虚拟机的调用技术。JNI意义:

  1. 支撑java平台无关性。
  2. 避免对既有的c/c++世界的工具重复造轮子。

二、整体调用结构

image.png

三、Java层职责

1.JNI库加载

JNI加载动作必须在方法调用之前,一般会在类的static代码块中进行。 System.loadLibrary(media_jni);

系统会根据不同平台,自动扩展为完整的库名称,例如:

平台名称库名称
Linuxlibmedia_jni.so
Windowsmedia_jni.dll

2.Native方法声明

通过在函数前添加“native”关键字,标注本方法由JNI层来实现。

privite static native final void native_init();

四、JNI层职责

1. 注册JNI函数

1.1 静态注册

javah工具,通过包含native方法的java.class生成JNI头文件。 image.png

当java层调用对应native方法时,JVM会根据命名规则,去寻找JNI方法,若找到,则建立关联关系(保存JNI函数指针,方便后续复用);否则报错。

弊端:

  1. 需要编译所有声明了native函数的java类。
  2. javah生成的jni函数名会特别长。
  3. 初次调用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的系统函数指针,用于

  1. 调用java函数
  2. 操作jobject对象等

JNIEnv对象在java调用native,和native调用java时获取方式不一样

  1. java函数转换为JNI函数后,由虚拟机传递过来
  2. 调用JavaVMAttachCurrentThread函数获取(在后台线程退出前,需要调用javaVMDetachCurrentThread函数来释放对应资源)

2.2 JNIEnv操作jobject

通过JNIEnv提供的getID接口,获取jfieldIDjmethodID;再通过callMethod、get/set field方法进行使用

成员类型获取ID使用方法
Fieldenv->GetFieldID(jclazz,fieldName,sig)NativeType Get<type>Field(JNIEnv,jobject,jfieldID)void Set<type>Field(JNIEnv,jobject,jfieldID,NativeType)
Methodenv->GetFieldID(jclazz,fieldName,sig)NativeType Call<Type>Method(JNIEnv,jobject,jmethodID,args...)

2.3 JNIEnv操作数组

image.png image.png

数组类型使用方法
intGetArrayLength;(*env)->GetIntArrayElementsSetIntArrayRegion
StringGetObjectArrayElementGetStringUTFChars

String[]相对特别,jni没有jstringArray,而是以jobjectArray承载,后面通过单个对象调用GetStringUTFChars方法获取char*对象,最终转换为char**对象。

3. 垃圾回收

java层的对象传递到native层之后,不能直接用局部变量保存后访问,原因是可能出现java层对象已释放,出现野指针的情况,所以需要native层进行特殊的引用处理。

3.1 JNI引用类型

引用类型回收时机使用用法使用场景
Local ReferenceJNI层函数返回后,可能被回收env-> NewLocalRef(jobject)使用频度最高,类比方法内的局部变量,无野指针问题;未及时回收的该对象,可能占用过多内存
Global ReferenceJNI层主动释放时才会回收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层抛出异常