JNI原理:

JNI是Java Native Interface,翻译为Java本地接口,是Java与其他语言通信的桥梁,当查询一些用Java技术无法处理的任务时,开发人员就可以使用JNI技术来完成。一般来说主要有以下情况需要用到JNI技术。

  • 需要调用Java语言不支持的依赖于操作系统特性的一些功能。
  • 为了整合一些以前的非Java语言开发的系统。
  • 为了节省程序的运行时间,必须采用其他语言来提升运行效率。

 

JNI不只是应用于Android开发,它还有非常广泛的应用场景。而在Android的应用场景一般有:音频开发,热修复,插件化,逆向开发,系统源码调用等等。为了更加方便的使用JNI技术,Android还提供了NDK这个工具集合。

 

 

 

系统源码中的JNI:

Android系统语言来划分的话由两个世界组成,分别是java世界和Native世界。这样划分的原因除了性能之外,还有在Java诞生之前,很多程序和库都是由Native语言写的。那么java世界的代码要怎么使用Notive世界的代码呢?答案是使用JNI。这里举例MediaRecorder框架。

 

 

 

1.MediaRecorder框架中的JNI:

在这里重点介绍MediaRecorder框架中JNI,Java Framework层对应的是MediaRecorder.java,JNI层对应的是libmedia_jni.so,它是一个JNI的动态库,Native层对应的是libmedia.so,这个动态库完成了实际上的调用功能。

 

 

 

1.1:Java Framework层的MediaRecorder:

与之相关的有:调用System.loadLibrary("media_jni")用来加载名称为media_jni的动态库,也就是libmedia_jni.so。接着调用native_init方法,其内部会调用Native方法,用来完成JNI的注册。然后再使用native修饰方法,说明它是一个native方法,表示由JNI实现。因为对于JavaFramework层来说只需要加载对应的JNI库,接着声明native方法就可以了,剩下的工作由JNI层来完成。

 

 

 

1.2:JNI层的MediaRecorder:

MediaRecorder的JNI层是由android_media_recorder.cpp实现的android_media_MediaRecorder_native_init方法是native_init方法在JNI层的实现,android_media_MediaRecorder_start方法则是start方法在JNI层的实现。那么native_init方法是怎么找到android_media_MediaRecorder_native_init方法的呢,这需要了解JNI注册。

 

 

 

2.Native方法注册:

Native方法注册分为静态注册动态注册,其中静态注册多用于NDK开发,而动态注册多用于Framework开发。

 

 

 

2.1:静态注册:

根据函数名找到对应的JNI函数;Java层调用某个函数时,会从对应的JNI中寻找该函数,如果没有就会报错,如果存在就会建立一个关联关系(其实就是保存JNI的函数指针),以后再调用时会直接使用这个函数,这部分的操作由虚拟机完成。

静态注册的建立:首先在Java代码中声明native函数,然后通过javac或者javah相关命令来生成native函数的对应的头文件,然后在c/c++文件中引用这些头文件,最后在JNI代码中实现这些函数的具体业务逻辑即可。

JNI的命名规范:返回值 + Java前缀+全路径类名+方法名+参数1JNIEnv+参数2jobject+其他参数

2.1.1:静态注册的缺点:

  • JNI的函数名称过长。
  • 声明Native方法的类需要生成头文件。
  • 初次调用Native方法时需要建立关联,影响效率。

 

 

 

2.2:动态注册:

JNI中有一种结构体用来记录Java的Native方法和JNI方法的映射关系,它就是JNINativeMethod,在jni.h中定义。JNI允许我们提供一个函数映射表,注册给Java虚拟机,这样JVM就可以用函数映射表来调用相应的函数。这样就可以不必通过函数名来查找需要调用的函数了。

 

动态注册的建立:在使用System.LoadLibrary函数会调用JNI_OnLoad函数,并且在这个函数里面去动态的注册native方法。在JNI_OnLoad函数中又主要调用了jniRegisterNativeMethods函数,该函数内部调用了RegisterNatives函数将注册函数的Java类,以及注册函数的数组(JNINativeMethod),以及个数注册在一起,这样就实现了绑定。

 

 

 

3.数据类型的转换:

 

Java的数据类型到了JNI层就需要转换为JNI层的数据类型。Java的数据类型分为引用数据类型和脚本数据类型,JNI层也对这两种数据类型进行了区分。

 

 

3.1:基本数据类型的转换:

 

基本数据类型的转换
JavaNativeSignature
bytejbyteB
charjcharC
doublejdoubleD
floatjfloatF
intjintI
shortjshortS
longjlongJ
booleanjbooleanZ
voidvoidV

 

 

 

 

3.2:引用数据类型的转换:

 

引用数据类型的转换
JavaNativeSignature
所有对象jobjectL+classname+;
ClassjclassLjava/lang/Class;
StringjstringLjava/lang/String;
ThrowablejthrowableLjava/lang/Throwable;
Object[]jobjectArray[L+classname+;
byte[]jbyteArray[B
char[]jcharArray[C
double[]jdoubleArray[D
float[]jfloatArray[F
int[]jintArray[I
short[]jshortArray[S
long[]jlongArray[J
boolean[]jbooleanArray[Z

 

 

 

 

4.方法签名:

因为Java是支持函数重载的,也就是说,可以定义相同方法名,但是不同参数的方法,然后Java根据其不同的参数,找到其对应的实现的方法。JNI要怎么支持这种,答案是---签名。即将参数类型和返回值类型的组合。如果拥有一个该函数的签名信息和这个函数的函数名,我们就可以顺序的找到对应的Java层中的函数了。

 

  • 方法签名的格式:(参数的签名格式,两个参数之间使用;分隔)返回值的签名格式。
  • 查看类中的方法签名使用javap命令。

 

 

 

 

5.解析JNIEnv:

 

JNIEnv是Native世界中Java环境的代表,通过JNIEnv*指针就可以在Native世界中访问Java世界的代码进行操作,它只在创建它的线程中有效,不能跨线程传递,因此不同线程的JNIEnv是彼此独立的。JNIEnv的主要作用有:

  • 调用java的方法。
  • 操作java中的变量或者对象等等。

 

 

JavaVM它是虚拟机在JNI层的代表,在一个虚拟机进程中只有一个JavaVM,因此该进程的所有的线程都可以使用这个JavaVM。通过JavaVM的AttachCurrentThread函数就可以获取这个线程的JNIEnv,还要记得在使用AttachCurrentThread函数的线程退出之前,调用DetachCurrentThread函数来释放资源。

JNIEnv是一个结构体,其内部又包含了JNIINativeInterface。在JNIEnv中定义了很多的函数,例如:FindClass函数用来找到Java中指定名称的类;GetObjectClass函数通过对象的实例获取class。GetMethodID函数用来获取Java中的方法;GetFieldID函数用来获取Java中的成员变量。JNIEnv的类型都和JNIINativeInterface结构有关联的,在JNIINativeInterface结构中定义了很多和JNIEnv结构体对应的函数指针,通过这些函数指针的定义,就可以确定到虚拟机的JNI函数表中,从而实现了JNI层在虚拟机中的函数调用,这样的话JNI层就可以调用Java世界的方法了。

 

 

 

5.1:jfieldID和jmethodID:

在JNIEnv结构体中定义了很多的函数,这些函数都会有不同的返回值。例如:GetMethodID函数它返回值为一个jmethodID,GetFieldID函数它返回值为一个jfieldID。除此之外还有很多的返回值。JNI层通过找到函数找到Java层的class对象之后,再获取到class的一些成员变量或者方法,赋值给jfieldID和jmethodID类型的变量,具体是jfieldID代表Java的成员变量和jmethodID代表Java的方法。这样做的原因有:

  • 如果每次调用相关的方法时都要查询方法和变量,显得效率低。
  • 这些方法和成员变量都是本地引用,为了高效使用。

 

 

 

6.引用类型:

 

与Java一样,JNI也有引用类型,分别是本地引用,全局引用和弱全局引用。

 

6.1:本地引用:

 

JNIEnv函数所返回的引用基本上都是本地引用,本地引用是JNI中最常见的引用类型,它的特点有:

  • 当Native函数返回时,这个本地引用就会被自动释放。
  • 只有在创建它的线程中才有效,不能跨线程使用。
  • 局部引用是JVM负责的引用类型,受JVM管理。
  • 我们可以调用DeleteLocalRef函数手动删除本地引用。

 

 

6.2:全局引用:

全局引用几乎是与本地引用相反的,它的特点有:

  • 在Native函数返回时不会被自动的释放,因此全局引用需要手动的进行释放,并且还不会被GC回收。
  • 全局引用是可以跨线程使用的。
  • 全局引用是不会受到JVM管理。
  • JNIEnv的NewGlobalRef函数用于创建全局引用;JNIEnv的DeleteGlobalRef函数用于释放全局引用。

 

 

6.3:弱全局引用:

弱全局引用是一种特殊的全局引用,它和全局引用的特定类似,不同的是弱全局引用是可以被GC回收的,弱全局引用被GC回收之后就会指向NULL。JNIEnv的NewWeakGlobalRef函数用于创建弱全局引用;JNIEnv的DeleteWeakGlobalRef函数用于释放弱全局引用。在使用它之前还要JNIEnv的IsSameObject函数来判断是不是已经被GC回收了。

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

mo@

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值