Android JNI—数据类型及对JNI理解

Publish by https://www.leachchen.com/

本节主要介绍下JNI的函数命名及参数说明,数据类型对应关系,描述符及对JNI的理解

JNI Java Native Interface(Java本地接口)。做JAVA或者Android的朋友可能会接触到JNI的开发,特别是涉及到音视频、与硬件设备有交互的时候、封装一些复杂逻辑或者算法、上层处理性能跟不上等情况下。JNI开发偏底层开发,编译出来的库的后缀为.so,它也更安全更难破解。JNI开发需要懂C/C++,可基于Android Studio、Eclipse,VS等开发工具进行开发,一般由C/C++开发者进行开发并且支持Android,IOS平台。若你又精通Android上层开发,又精通JNI底层开发,那你的级别又将上升一个档次。

1. 函数命名及参数说明

第一篇文章提到过静态注册和动态注册,若是静态注册,则命名需要为固定格式,动态注册则不需要,这里以静态注册方式来说:

JNIEXPORT jstring JNICALL Java_com_leachchen_testjni_MainActivity_testMethod(JNIEnv *env, jobject instance, jstring name_)

a.函数参数

Java是函数的前缀,com_leachchen_testjni_MainActivity是函数所在类路径,testMethod是方法名;

第一个参数:JNIEnv* 是定义任意native函数的第一个参数(包括调用JNI的RegisterNatives函数注册的函数),指向JVM函数表的指针,函数表中的每一个入口指向一个JNI函数,每个函数用于访问JVM中特定的数据结构。

第二个参数:调用java中native方法的实例或Class对象,如果这个native方法是实例方法,则该参数是jobject,如果是静态方法,则是jclass

第三个参数:Java对应JNI中的数据类型,Java中String类型对应JNI的jstring类型。

b.函数返回值

JNIEXPORT和JNICALL宏中间的jstring,表示函数的返回值类型,对应Java的String类型

2. 数据类型对应关系:

在调用Java native方法将实参传递给C/C++函数的时候,会自动将java形参的数据类型自动转换成C/C++相应的数据类型,所以我们在写JNI程序的时候,必须要明白它们之间数据类型的对应关系。

a.基本数据类型

Java Type Native type Description
boolean jboolean unsigned 8 bits
byte jbyte signed 8 bits
char jchar unsigned 16 bits
short jshort signed 16 bits
int jint signed 32 bits
long jlong signed 64 bits
float jfloat 32 bits
double jdouble 64 bits

b.引用类型

Java Type Native type Description
all object jobject  
java.lang.Class instances jstring  
arrays jarray  
Object[] jobjectArray  
boolean[] jbooleanArray  
byte[] jbyteArray  
char[] jcharArray  
int[] jintArray  
long[] jlongArray  
float[] jfloatAyyay  
double[] jdoubleArray  
java.lang.Throwable Objects jthrowable  

3. 描述符

a.类描述符

类描述符用来表示一个类或者接口的名字。把类或者接口在java中所定义的完整名称中的”.”替换成”/”就是类描述符。
比如java.lang.String的类描述符为:java/lang/string
数组类的描述符:在”[“后面跟着数组元素的类型的字段描述符,如:
“int[]”的类描述符为:“[I”
“double[][][]”的类描述符为:”[[[D”

b.字段描述符:

Java Type Native type Description
boolean Z  
byte B  
char C  
short S  
int I  
long J  
float F  
double D  

引用类型的字段描述符的第一个字符是”L”,接着写类描述符,最后以”;”结尾。
数组类型的字段描述符的定义规则和数组类描述符一致。
下面的例子是引用类型的字段描述符和他们相对应的java类型:

Java Type Native type Description
String “Ljava/lang/String;”  
int[] “[I”  
Object[] “[Ljava/lang/Object;”  

c.方法描述符

  • 方法描述符首先在”()”中写所有的参数类型的字段描述符,然后在”()”后面接着写返回类型的字段描述符。
  • 并且在参数类型的字段描述符之间不能有空格或者其他分隔符。
  • “V”用来表示没有返回类型。
  • 构造函数使用”V”做为返回类型,并且使用”< init>”做为函数名。
Java Type Native type Description
String f(); ”()Ljava/lang/String;”  
long f(int i, Class c); “(ILjava/lang/Class;)J”  
String(byte[] bytes); ”([B)V”  

4. 理解

a.函数参数 (JNIEnv *env, jobject instance )理解

基本类型很容易理解,就是对C/C++中的基本类型用typedef重新定义了一个新的名字,在JNI中可以直接访问。 JNI把Java中的所有对象当作一个C指针传递到本地方法中,这个指针指向JVM中的内部数据结构,而内部的数据结构在内存中的存储方式是不可见的。只能从JNIEnv指针指向的函数表中选择合适的JNI函数来操作JVM中的数据结构。如访问java.lang.String对应的JNI类型jstring时,没有像访问基本数据类型一样直接使用,因为它在Java是一个引用类型,所以在本地代码中只能通过GetStringUTFChars这样的JNI函数来访问字符串的内容。如:

testMethod(JNIEnv *env, jobject instance, jstring name_)

Java内部的数据结构(除基本数据类型外)对JNI来说是不可见的,如上面函数中,Java传递过来了一个字符串类型的name_变量,name_变量在JVM中的数据结构对JNI来说是不可见的,那JNI如何访问到这个变量呢?*env指针指向的函数表中的GetStringUTFChars变能访问到JVM中的数据结构,调用GetStringUTFChars可以获取到该字符串的值,然后再赋值给JNI中的变量,这样便完成了JAVA-》JNI的一个传值过程。若JNI想返回一个字符串给JAVA,那么需要调用NewStringUTF,将JNI中的字符串,包装成符合JAVA字符串数据结构,的字符串,然后返回JVM便能够识别到了。

jobject instance
如果native方法不是static的话,这个obj就代表这个native方法的类实例 如果native方法是static的话,这个obj就代表这个native方法的类的class对象实例(static方法不需要类实例的,所以就代表这个类的class对象)

b.调用GetStringUTFChars后需要调用ReleaseStringUTFChars

在调用GetStringUTFChars函数从JVM内部获取一个字符串之后,JVM内部会分配一块新的内存,用于存储源字符串的拷贝,以便本地代码访问和修改。即然有内存分配,用完之后马上释放是一个编程的好习惯。通过调用ReleaseStringUTFChars函数通知JVM这块内存已经不使用了,你可以清除了。注意:这两个函数是配对使用的,用了GetXXX就必须调用ReleaseXXX,而且这两个函数的命名也有规律,除了前面的Get和Release之外,后面的都一样。

Leach Chen

Leach Chen

I am an Android developer.I will add description latter.