Android深入理解JNI(二)类型转换、方法签名和JNIEnv
前言
1.数据类型的转换
首先给出上一篇文章中android_media_MediaRecorder.cpp中的android_media_MediaRecorder_start方法: frameworks/base/media/jni/android_media_MediaRecorder.cpp
android_media_MediaRecorder_start方法有一个参数为jobject类型,它是JNI层的数据类型,Java的数据类型到了JNI层就需要转换为JNI层的数据类型。Java的数据类型分为基本数据类型和引用数据类型,JNI层对于这两种类型也做了区分,我们先来查看基本数据类型的转换。
1.1 基本数据类型的转换
Java |
Native |
Signature |
---|---|---|
byte |
jbyte |
B |
char |
jchar |
C |
double |
jdouble |
D |
float |
jfloat |
F |
int |
jint |
I |
short |
jshort |
S |
long |
jlong |
J |
boolean |
jboolean |
Z |
void |
void |
V |
从上表可以可看出,基本数据类型转换,除了void,其他的数据类型只需要在前面加上“j”就可以了。第三列的Signature 代表签名格式,后文会介绍它。接着来看引用数据类型的转换。
1.2 引用数据类型的转换
Java |
Native |
Signature |
---|---|---|
所有对象 |
jobject |
L+classname +; |
Class |
jclass |
Ljava/lang/Class; |
String |
jstring |
Ljava/lang/String; |
Throwable |
jthrowable |
Ljava/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 |
从上表可一看出,数组的JNI层数据类型需要以“Array”结尾,签名格式的开头都会有“[”。除了数组以外,其他的引用数据类型的签名格式都会以“;”结尾。 另外,引用数据类型还具有继承关系,如下所示:
再来列举MediaRecorder框架的Java方法: frameworks/base/media/java/android/media/MediaRecorder.java
_setOutputFile方法对应的JNI层的方法为: frameworks/base/media/jni/android_media_MediaRecorder.cpp
对比这两个方法可以看到,FileDescriptor类型转换为了jobject类型 ,long类型转换为了jlong类型。
2.方法签名
前面表格已经列举了数据类型的签名格式,方法签名就由签名格式组成,那么,方法签名有什么作用呢?我们看下面的代码。 frameworks/base/media/jni/android_media_MediaRecorder.cpp
gMethods数组中存储的是MediaRecorder的Native方法与JNI层方法的对应关系,其中
”()V”和 “(Ljava/lang/Object;Ljava/lang/String;Ljava/lang/String;)V”就是方法签名。我们知道Java是有重载方法的,可以定义方法名相同,但参数不同的方法,正因为如此,在JNI中仅仅通过方法名是无法找到 Java中的具体方法的,JNI为了解决这一问题就将参数类型和返回值类型组合在一起作为方法签名。通过方法签名和方法名就可以找到对应的Java方法。
JNI的方法签名的格式为:
(参数签名格式...)返回值签名格式 |
---|
拿上面gMethods数组的native_setup方法举例,他在Java中是如下定义的:
它在JNI中的方法签名为:
如果我们每次编写JNI时都要写方法签名,也会是一件比较头疼的事,幸好Java提供了javap命令来自动生成方法签名。我们先写一个简单的MediaRecorder.java包含上面的native_setup方法:参照本文第一节给出的类型转换表格,native_setup方法的第一个参数的签名为“Ljava/lang/Object;”,后两个参数的签名为“Ljava/lang/String;”,返回值类型void 的签名为“V”,组合起来就是上面的方法签名。
这个文件的在我的本地地址为D:/Android/MediaRecorder.java,接着执行如下命令:
javac D:/Android/MediaRecorder.java |
---|
执行命令后会生成MediaRecorder.class文件,最后使用javap命令:
javap -s -p D:/Android/MediaRecorder.class |
---|
其中s
表示输出内部类型签名,p
表示打印出所有的方法和成员(默认打印public成员),最终会在cmd中的打印结果如下:
可以很清晰的看到输出的native_setup方法的签名和此前给出的一致。
3.JNIEnv
JNIEnv 是一个指向全部JNI方法的指针,该指针只在创建它的线程有效,不能跨线程传递,因此,不同线程的JNIEnv是彼此独立的,JNIEnv的主要作用有两点: 1.调用Java的方法。 2.操作Java(获取Java中的变量和对象等等)。
先来看JNIEnv的定义,如下所示。 libnativehelper/include/nativehelper/jni.h
这里使用预定义宏__cplusplus
来区分C和C++两种代码,如果定义了__cplusplus
,则是C++代码中的定义,否则就是C代码中的定义。在这里我们也看到了JavaVM,它是虚拟机在JNI层的代表,在一个虚拟机进程中只有一个JavaVM,因此,该进程的所有线程都可以使用这个JavaVM。通过JavaVM的AttachCurrentThread函数可以获取这个线程的JNIEnv,这样就可以在不同的线程中调用Java方法了。还要记得在使用AttachCurrentThread函数的线程退出前,务必要调用DetachCurrentThread函数来释放资源。
jfieldID和jmethodID
在JNI中用jfieldID和jmethodID来代表Java类中的成员变量和方法,可以通过JNIEnv的下面两个方法来分别得到:
其中,jclass代表Java类,name代表成员方法或者成员变量的名字,sig为这个方法和变量的签名。我们来查看MediaRecorder框架的JNI层是如何使用上述的两个方法的,如下所示。
frameworks/base/media/jni/android_media_MediaRecorder.cpp
注释1处,通过FindClass来找到Java层的MediaRecorder的Class对象,并赋值给jclass类型的变量clazz,因此,clazz就是Java层的MediaRecorder在JNI层的代表。注释2和注释3处的代码用来找到Java层的MediaRecorder中名为mNativeContext和mSurface的成员变量,并分别赋值给context和surface。注释4出获取Java层的MediaRecorder中名为postEventFromNative的静态方法,并赋值给post_event。其中fields的定义为:
将这些成员变量和方法赋值给jfieldID和jmethodID类型的变量主要是为了效率考虑,如果每次调用相关方法时都要进行查询方法和变量,显然会效率很低,因此在MediaRecorder框架JNI层的初始化方法android_media_MediaRecorder_native_init中将这些jfieldID和jmethodID类型的变量保存起来,以供后续使用。
使用jfieldID和jmethodID
我们保存了jfieldID和jmethodID类型的变量,接着怎么使用它们呢,如下所示。 frameworks/base/media/jni/android_media_MediaRecorder.cpp
在注释1处调用了JNIEnv的CallStaticVoidMethod函数,其中就传入了fields.post_event,从上面我们得知,它其实是保存了Java层MediaRecorder的静态方法postEventFromNative: frameworks/base/media/java/android/media/MediaRecorder.java
这样我们就能在JNI层中访问Java的静态方法了。同理,如果想要访问Java的方法则可以使用JNIEnv的CallVoidMethod函数。上面的例子是使用了jmethodID,接着来查看jfieldID:
frameworks/base/media/jni/android_media_MediaRecorder.cpp
在注释1处调用了JNIEnv的GetObjectField函数,参数中的fields.surface用来保存Java层MediaRecorde中的成员变量mSurface,mSurface的类型为Surface,这样通过GetObjectField函数就得到了mSurface在JNI层中对应的jobject类型变量surface 。
- javascript实例:逐条记录停顿的走马灯
- Python标准库05 存储对象 (pickle包,cPickle包)
- macOS平台下虚拟摄像头的研发总结
- 网页优化系列三:使用压缩后置viewstate
- 网页优化系列三:使用压缩后置viewstate
- macOS下利用dSYM文件将crash文件中的内存地址转换为可读符号
- 微信小程序的大动作
- Python标准库04 文件管理 (部分os包,shutil包)
- 手把手教你Dojo入门
- location的hash部分和使用window.onhashchange实现ajax请求内容时使用浏览器后退和前进功能
- 协议森林01 邮差与邮局 (网络协议概观)
- Mac OS平台下应用程序安装包制作工具Packages的使用介绍
- 协议森林02 小喇叭开始广播 (以太网与WiFi协议)
- 信号与频谱
- java教程
- Java快速入门
- Java 开发环境配置
- Java基本语法
- Java 对象和类
- Java 基本数据类型
- Java 变量类型
- Java 修饰符
- Java 运算符
- Java 循环结构
- Java 分支结构
- Java Number类
- Java Character类
- Java String类
- Java StringBuffer和StringBuilder类
- Java 数组
- Java 日期时间
- Java 正则表达式
- Java 方法
- Java 流(Stream)、文件(File)和IO
- Java 异常处理
- Java 继承
- Java 重写(Override)与重载(Overload)
- Java 多态
- Java 抽象类
- Java 封装
- Java 接口
- Java 包(package)
- Java 数据结构
- Java 集合框架
- Java 泛型
- Java 序列化
- Java 网络编程
- Java 发送邮件
- Java 多线程编程
- Java Applet基础
- Java 文档注释
- 阔别两年,webpack 5 正式发布了!
- NumPy 数据归一化、可视化
- 迭代器怎么就节省内存了?
- Go 多版本管理
- 空谈分布式系统设计之幂等性
- 空谈发件箱模式(outbox pattern)
- 《一起学sentinel》六、Slot的子类及实现之FlowSlot和DegradeSlot
- Hive UDF/UDAF 总结
- 3分钟短文:用Laravel发一封“漂洋过海”的电子邮件
- leetcode之单词替换
- KMP算法分析
- Spring Boot 系列:最新版优雅停机详解
- 前端学数据结构与算法(八): 单词前缀匹配神器-Trie树的实现及其应用
- 突击并发编程JUC系列-Locksupport 与 Condition
- 01.视频播放器框架介绍