1.jni简介
native c/c++代码是和平台相关联的,是可以直接运行的,在java出现以前很多软件由native语言编写,包括Jvm也是由native语言编写的,java提供了一套jni技术使得native和java相互调用。
根据oracle文档,如下情况需要使用jni:
- The standard Java class library does not support the platform-dependent features needed by the application.
- You already have a library written in another language, and wish to make it accessible to Java code through the JNI.
- You want to implement a small portion of time-critical code in a lower-level language such as assembly.
使用jni可以:
- Create, inspect, and update Java objects (including arrays and strings).
- Call Java methods.
- Catch and throw exceptions.
- Load classes and obtain class information.
- Perform runtime type checking.
使用jni的Invoication API可以把jvm嵌入native application中,而无需接触jvm的源码。
2.native函数和java函数关联映射
jni java部分,MediaScanner.java:
public class MediaScanner
{ //①加载对应的JNI库,media_jni是JNI库的名字。实际加载动态库的时候会拓展成libmedia_jni.so,在Windows平台上将拓展为media_jni.dll。
static{
System.loadLibrary("media_jni");
native_init();//调用native_init函数
}
//②声明一个native函数。native为Java的关键字,表示它将由JNI层完成,java仅声明函数即可。
private static native final void native_init();
private native void processFile(String path, String mimeType,MediaScannerClient client);
}
jni native部分,android_media_MediaScanner.cpp:
//①这个函数是native_init的JNI层实现。
static void android_media_MediaScanner_native_init(JNIEnv *env)
{
jclass clazz;
clazz= env->FindClass("android/media/MediaScanner");
fields.context = env->GetFieldID(clazz, "mNativeContext","I");
return;
}
//这个函数是processFile的JNI层实现。
static void android_media_MediaScanner_processFile(JNIEnv*env, jobject thiz,
jstring path, jstring mimeType, jobject client)
{
MediaScanner*mp = (MediaScanner *)env->GetIntField(thiz, fields.context);
constchar *pathStr = env->GetStringUTFChars(path, NULL);
if(mimeType) {
env->ReleaseStringUTFChars(mimeType, mimeTypeStr);
}
}
java和native的映射注册:
静态注册:
当第一次使用到某一个native函数的时候jvm会找对应的native函数,如果没有找到native函数,就会报错;如果找到了则建立两者映射关系(一个结构体,分别存储了java方法名和native方法名),以后再调用这个native函数可以直接使用映射关系直接找到了。 初次调用native函数时要根据函数名字搜索对应的JNI层函数来建立关联关系,这样会影响第一次调用的运行效率。动态注册可以解决这个问题。
动态注册:
SystemloadLibrary完成之后会尝试回调对应加载库中的jint JNI_OnLoad(JavaVM* vm, void* reserved)
方法,native库可以选择性的实现该方法,在该方法中自行向jvm注册native函数和java函数的映射关系。如此便避免了静态注册的第一次使用还需要查找的问题,这称之为动态注册。
android的libmedia_jni.so的动态注册在android_media_MediaPlayer.cpp
中实现。
jint JNI_OnLoad(JavaVM* vm, void* reserved)
{
//该函数的第一个参数类型为JavaVM,这可是虚拟机在JNI层的代表喔,每个Java进程只有一个
JNIEnv* env = NULL;
jintresult = -1;
if(vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
goto bail;
}
//动态注册MediaScanner的JNI函数。
if(register_android_media_MediaScanner(env) < 0) {
goto bail;
}
returnJNI_VERSION_1_4;//必须返回大于1.1的值,否则会报错。
}
值得注意的是该方法默认返回JNI_VERSION_1_1,而这个方法是在1_2中被添加的,需要返回1.2及以上版本才可以使用新的方法和特性。
The VM calls JNI_OnLoad when the native library is loaded (for example, through System.loadLibrary). JNI_OnLoad must return the JNI version needed by the native library.
In order to use any of the new JNI functions, a native library must export a JNI_OnLoad function that returns JNI_VERSION_1_2. If the native library does not export a JNI_OnLoad function, the VM assumes that the library only requires JNI version JNI_VERSION_1_1. If the VM does not recognize the version number returned by JNI_OnLoad, the native library cannot be loaded.
引用自oracle文档网站:
https://docs.oracle.com/javase/8/docs/technotes/guides/jni/jni-12.html#JNI_OnLoad
3.java和jni类型对应及类型签名对应
原始类型:
引用类型对应:
类型签名用于使用字符串形式描述方法定义供jvm解析,其对应列表如下:
4.native函数运行时参数和注意事项
- jni调用可以理解为同步调用,JniEnv是基于特定线程的。代表函数运行时的上下文环境,jni提供的所有函数方法几乎都需要通过它来使用,需要注意的是不同的线程具有不同的JniEnv,所以不能将其保存为全局引用以期望另外的线程可以访问。如果当前线程还未具有JniEnv,需要先将其绑定到jvm来获取对应的JniEnv,运行完需要解绑线程。
- jni引用变量分为局部、全局、和全局弱引用。全局引用不会被java的gc回收,全局弱引用存在被回收的可能,局部引用当方法结束后自动被回收,方法参数的引用都是局部引用,方法中定义的引用都是局部引用,局部引用可以手动立刻解除占用提高效率,如下:
for(inti = 0; i < 100; i++)
{
jstring pathStr = mEnv->NewStringUTF(path);
......//做一些操作
//mEnv->DeleteLocalRef(pathStr); //不立即释放Local Reference会导致创建100个局部引用,直到方法结束才会释放。
}
jni中的参数。会比java中的额外多两个参数,第一个参数一定是JniEnv引用,第二个参数根据是否是静态方法有所不同。如果java世界声明该方法是非静态的,则为java对象引用,如果是静态的则为对应的java class引用。
jfieldID和jmethodID。对应java世界的类字段和方法,jni提供方法可以在java的方法和jmethodID之间相互转换,jfildD也提供了相关的转换方法。
jni中的异常。因为大部分c库不确保运行时参数没有错误,如果确保可能会导致判断两次降低效率,所以参数无误应该有调用者来确保。jni遇见异常之后并不像Java一样终止运行,会记录异常并继续运行,直到从JNI层返回到Java层后,虚拟机才会抛出这个异常。jni函数可以在合适的时刻执行ExceptionCheck()来主动检测有无异常被记录,并选择return来终止函数的运行。此外,jni可以主动抛出任意java世界的异常。jni有两种方式可以处理异常:a.使用ThrowNew抛出自定义异常并使用return返回,或者干脆直接return让java代码抛出对应异常;b.使用ExceptionClear()清除异常,并自己处理掉异常。
Most C library functions do not guard against programming errors. The
printf()
function, for example, usually causes a runtime error, rather than returning an error code, when it receives an invalid address. Forcing C library functions to check for all possible error conditions would likely result in such checks to be duplicated--once in the user code, and then again in the library.The programmer must not pass illegal pointers or arguments of the wrong type to JNI functions
参考链接:
https://docs.oracle.com/en/java/javase/11/docs/specs/jni/index.html
http://wiki.jikexueyuan.com/project/deep-android-v1/jni.html