在Java开发中,使用到JNI的机会比较少,每次见到JNI代码都有点陌生,需要查找一些之前看过的资料温习一下,为了提高以后的效率这里记录下JNI常见的一些知识点和代码模式。
初始化
Java中的初始化
Java中,要使用JNI,首先必须让JVM加载动态库,在Linux平台下动态库为so文件,Windows平台下为.dll,mac下为.dylib。
C中的初始化
C中,一般来说要做两类工作:
给JVM注册Java可调用的C函数;
- 必须使用JNIEXPORT标记暴露给JVM调用的函数,避免被strip;
- 可以使用JVM的自动注册机制,也可以通过JVM加载回调手动注册;
- 自动注册机制:只要C函数命名符合JNI要求的命名规范,JVM在加载动态库时就会自动查找指定native函数对应的C函数,如果找到就自动注册;
- 手动调用JNI函数注册方法:也可以在动态库中定义JNI_OnLoad函数,这个函数会在动态库被JVM加载后被调用,这时可以手动调用JNI定义的RegisterNatives函数注册C函数。
向JVM查询Java类信息,并记录下来查询结果,后续不需要再次查询直接使用即可。
- C向Java回调时,需要用到查询结果,通常在C++中使用一个Manager/Wrapper/Helper类封装真正的C++类、获取到的Java类信息、以及其他JNI特定的信息;
- Java通过native方法调用C++时,往往需要定义几个标准方法:
- init方法用于初始化Java类在中C++对应的类实例,并将C++类实例指针通过Java中的一个long类型变量保存,后续调用时需要java传入这个long以在C中找回类实例指针,注意C++中应该使用reinterpret_cast
来做从long类型到指针类型的类型强制转换; - destroy方法用于析构和释放C++对应的类实例,destroy之后Java中也不再应该使用对应Java类对象,应该尽快让对象被gc。
- init方法用于初始化Java类在中C++对应的类实例,并将C++类实例指针通过Java中的一个long类型变量保存,后续调用时需要java传入这个long以在C中找回类实例指针,注意C++中应该使用reinterpret_cast
- 一些版本的JVM实现不会自动释放不再使用的局部Java变量(Java变量指的是通过JNI函数返回的引用JVM相关对象的变量),必须要等到JNI方法执行完毕才能释放,如果在一个方法中存在循环,产生了大量的局部对象,却不手动释放的话,就会发生异常导致崩溃。
- 可以封装一个C++类ScopedLocalRef:在构造函数中保存局部Java变量,提供get方法用于获取局部变量,并在析构时调用JNI的局部引用释放函数释放局部变量,此后在JNI中需要使用局部Java变量时,都用封装的类而不直接用Java变量。
Chromium项目jni代码生成
https://chromium.googlesource.com/chromium/src/+/refs/heads/main/base/android/jni_generator