JNI的数据类型包含两种:基本类型和引用类型。
基本类型主要有jboolean
、jchar
、jint
等,它们和Java中的数据类型的对应关系如下表所示。
JNI中的引用类型主要有类、对象和数组,它们和Java中的引用类型的对应关系如下表所示。
当然,JNI 中还有个 Java 中没有的 jsize
,定义如下:
typedef jint jsize;
其实jsize
整型是用来描述基本指标和大小,没有什么神秘的。
JNI的类型签名标识了一个特定的Java类型,这个类型既可以是类和方法,也可以是数据类型。
类的签名比较简单,它采用 “L+包名+类名+;
” 的形式,只需要将其中的.
替换为/
即可。比如java.lang.String
,它的签名为Ljava/lang/String;
,注意末尾的;
也是签名的一部分。
基本数据类型的签名采用一系列大写字母来表示,如下表所示。
从上表可以看出,基本数据类型的签名是有规律的,一般为首字母的大写,但是boolean
除外,因为B
已经被byte
占用了,而long
的签名之所以不是L
,那是因为L
表示的是类的签名。
对象和数组的签名稍微复杂一些。对于对象来说,它的签名就是对象所属的类的签名,比如String
对象,它的签名为Ljava/lang/String;
。对于数组来说,它的签名为[+类型签名
,比如int
数组,其类型为int
,而int
的签名为I
,所以int
数组的签名就是[I
,同理就可以得出如下的签名对应关系:
char[] [C
float[] [F
double[] [D
long[] [J
String[] [Ljava/lang/String;
Object[] [Ljava/lang/Object;
对于多维数组来说,它的签名为n个[+类型签名
,其中n表示数组的维度,比如,int[][]
的签名为[[I
,其他情况可以依此类推。
方法的签名为(参数类型签名)+返回值类型签名
,这有点不好理解。举个例子,如下方法:boolean fun1(int a, double b, int[] c)
,根据签名的规则可以知道,它的参数类型的签名连在一起是ID[I
,返回值类型的签名为Z
,所以整个方法的签名就是(ID[I)Z
。再举个例子,下面的方法:boolean fun1(int a, String b, int[] c)
,它的签名是(ILjava/lang/String; [I)Z
。为了能够更好地理解方法的签名格式,下面再给出两个示例:
int fun1() 签名为 ()I
void fun1(int i) 签名为 (I)V
一个Java类的方法的Signature可以通过javap
命令获取:javap -s -p Java类名
从java程序中传过去的String对象在本地方法中对应的是jstring
类型,jstring
类型和c中的char*
不同,所以如果你直接当做 char*
使用的话,就会出错。因此在使用之前需要将jstring
转换成为c/c++中的char*
,这里使用JNIEnv的方法转换。下面是一个例子:
JNIEXPORT jstring JNICALL Java_Prompt_getLine(JNIEnv *env, jobject obj, jstring prompt)
{
char buf[128];
const char *str = (*env)->GetStringUTFChars(env, prompt, 0);
printf("%s", str);
(*env)->ReleaseStringUTFChars(env, prompt, str);
}
这里使用GetStringUTFChars
方法将传进来的prompt
(jstring
类型)转换成为UTF-8
的格式,就能够在本地方法中使用了。
注意:在使用完你所转换之后的对象之后,需要显示调用ReleaseStringUTFChars
方法,让JVM释放转换成UTF-8的string的对象的空间,如果不显示的调用的话,JVM中会一直保存该对象,不会被垃圾回收器回收,因此就会导致内存溢出。
下面是访问String的一些方法:
GetStringUTFChars
将jstring
转换成为UTF-8
格式的char*
GetStringChars
将jstring
转换成为Unicode
格式的char*
ReleaseStringUTFChars
释放指向UTF-8
格式的char*
的指针ReleaseStringChars
释放指向Unicode
格式的char*
的指针NewStringUTF
创建一个UTF-8
格式的String
对象NewString
创建一个Unicode
格式的String
对象GetStringUTFLength
获取UTF-8
格式的char*
的长度GetStringLength
获取Unicode
格式的char*
的长度和String对象一样,在本地方法中不能直接访问jarray
对象,而是使用JNIEnv
指针指向的一些方法来使用。
访问Java原始类型数组:
JNIEXPORT jint JNICALL Java_IntArray_sumArray(JNIEnv *env, jobject obj, jintArray arr)
{
int i, sum = 0;
jsize len = (*env)->GetArrayLength(env, arr);
}
这里获取数组的长度和普通的c语言中的获取数组长度不一样,这里使用JNIEnv
的一个函数GetArrayLength
。
jint *body = (*env)->GetIntArrayElements(env, arr, 0);
使用GetIntArrayElements
方法获取指向arr
数组元素的指针,注意该函数的参数,第一个是JNIEnv
,第二个是数组,第三个是数组里面开始的元素。
for (i=0; i<len; i++) {
sum += body[i];
}
这里使用就和普通的c中的数组使用没有什么不同了
(*env)->ReleaseIntArrayElements(env, arr, body, 0);
和操作String中的释放String的引用是一样的,提醒JVM回收arr数组元素的引用。
这里举的例子是使用int数组的,同样还有boolean、float等对应的数组。
获取数组和释放数组元素指针的对应关系:
数组类型 | 获取函数 | 释放函数 |
---|---|---|
boolean | GetBooleanArrayElements | ReleaseBooleanArrayElements |
byte | GetByteArrayElements | ReleaseByteArrayElements |
char | GetCharArrayElements | ReleaseCharArrayElements |
short | GetShortArrayElements | ReleaseShortArrayElements |
int | GetIntArrayElements | ReleaseIntArrayElements |
long | GetLongArrayElements | ReleaseLongArrayElements |
float | GetFloatArrayElements | ReleaseFloatArrayElements |
double | GetDoubleArrayElements | ReleaseDoubleArrayElements |
GetObjectArrayElement
returns the object element at a given index.SetObjectArrayElement
updates the object element at a given index.JNI调用Java方法的流程是先通过类名找到类,然后再根据方法名找到方法的id
,最后就可以调用这个方法了。如果是调用Java中的非静态方法,那么需要构造出类的对象后才能调用它。
在本地方法中调用Java对象的方法的步骤:
① 获取你需要访问的Java对象的Class
类:
jclass cls = (*env)->GetObjectClass(env, obj);
使用GetObjectClass
方法获取obj
对应的jclass
。
② 获取MethodID
:
jmethodID mid = (*env)->GetMethodID(env, cls, "callback", "(I)V");
使用GetMethdoID
方法获取你要使用的方法的MethdoID
。其参数的意义:
env
:JNIEnv
cls
:第一步获取的jclass
"callback"
:要调用的方法名"(I)V"
:方法的Signature③ 调用方法:
(*env)->CallVoidMethod(env, obj, mid, depth);
使用CallVoidMethod
方法调用方法。参数的意义:
env
:JNIEnv
指针obj
:调用该native方法的jobject
对象mid
:方法的methodID
(即第二步获得的MethodID
)depth
:方法需要的参数(对应方法的需求,添加相应的参数),可以是可变参数注:这里使用的是CallVoidMethod
方法调用,因为没有返回值,如果有返回值的话要使用对应的方法。
除了CallVoidMethod
外,针对每种基本类型的方法都有不同的重载,如下:
jobject (*CallObjectMethod)(JNIEnv*, jobject, jmethodID, ...);
jobject (*CallObjectMethodV)(JNIEnv*, jobject, jmethodID, va_list);
jobject (*CallObjectMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);
jboolean (*CallBooleanMethod)(JNIEnv*, jobject, jmethodID, ...);
jboolean (*CallBooleanMethodV)(JNIEnv*, jobject, jmethodID, va_list);
jboolean (*CallBooleanMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);
jbyte (*CallByteMethod)(JNIEnv*, jobject, jmethodID, ...);
jbyte (*CallByteMethodV)(JNIEnv*, jobject, jmethodID, va_list);
jbyte (*CallByteMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);
jchar (*CallCharMethod)(JNIEnv*, jobject, jmethodID, ...);
jchar (*CallCharMethodV)(JNIEnv*, jobject, jmethodID, va_list);
jchar (*CallCharMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);
jshort (*CallShortMethod)(JNIEnv*, jobject, jmethodID, ...);
jshort (*CallShortMethodV)(JNIEnv*, jobject, jmethodID, va_list);
jshort (*CallShortMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);
jint (*CallIntMethod)(JNIEnv*, jobject, jmethodID, ...);
jint (*CallIntMethodV)(JNIEnv*, jobject, jmethodID, va_list);
jint (*CallIntMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);
jlong (*CallLongMethod)(JNIEnv*, jobject, jmethodID, ...);
jlong (*CallLongMethodV)(JNIEnv*, jobject, jmethodID, va_list);
jlong (*CallLongMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);
jfloat (*CallFloatMethod)(JNIEnv*, jobject, jmethodID, ...);
jfloat (*CallFloatMethodV)(JNIEnv*, jobject, jmethodID, va_list);
jfloat (*CallFloatMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);
jdouble (*CallDoubleMethod)(JNIEnv*, jobject, jmethodID, ...);
jdouble (*CallDoubleMethodV)(JNIEnv*, jobject, jmethodID, va_list);
jdouble (*CallDoubleMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);
void (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...);
void (*CallVoidMethodV)(JNIEnv*, jobject, jmethodID, va_list);
void (*CallVoidMethodA)(JNIEnv*, jobject, jmethodID, jvalue*);
给调用的函数传参数:
通常我们直接在methodID
后面将要传的参数添加在后面,但是还有其他的方法也可以传参数:
CallVoidMethodV
可以获取一个数量可变的列表作为参数;CallVoidMethodA
可以获取一个union。调用静态方法:
就是将第二步和第三步调用的方法改为对应的:
GetStaticMethodID
获取对应的静态方法的IDCallStaticIntMethod
调用静态方法调用静态方法应该使用对应的CallStaticTypeMethod
, 其中的Type
随着返回值类型不同而改变(参考前面非静态方法列出的类型)。
// 首先需要在Java中定义一个静态方法供JNI调用,如下所示。
public static void methodCalledByJni(String msgFromJni) {
Log.d(TAG, "methodCalledByJni, msg: " + msgFromJni);
}
// 然后在JNI中调用上面定义的静态方法:
void callJavaMethod(JNIEnv *env, jobject thiz) {
jclass clazz = env->FindClass("com/ryg/JniTestApp/MainActivity");
if (clazz == NULL) {
printf("find class MainActivity error! ");
return;
}
jmethodID id = env->GetStaticMethodID(clazz, "methodCalledByJni",
"(Ljava/lang/String; )V");
if (id == NULL) {
printf("find method methodCalledByJni error! ");
}
jstring msg = env->NewStringUTF("msg send by callJavaMethod in
test.cpp.");
env->CallStaticVoidMethod(clazz, id, msg);
}
访问Java对象的属性和访问Java对象的方法基本上一样,只需要将函数里面的Method
改为Field
即可。
GetFieldID
:获取某个属性的id
GetStaticFieldID
:获取某个静态属性的id
Class
对象为了能够在C/C++中调用Java中的类,jni.h
的头文件专门定义了jclass
类型表示Java中Class类。JNIEnv
中有3个函数可以获取jclass
:
jclass FindClass(const char* clsName)
:通过类的名称来获取jclass。注意,是类的全名,这时候包名不是用’"
.“点号而是用”/
"来区分的。
比如: jclass jcl_string=env->FindClass("java/lang/String");
来获取Java中的String对象的class对象
jclass GetObjectClass(jobject obj)
:通过对象实例来获取jclass
,相当于Java中的getClass()
函数
jclass getSuperClass(jclass obj)
:通过jclass
可以获取其父类的jclass
对象
上层定义一个native的方法,需要一个int 参数 ,返回一个int值;JNI 对应上层的方法 , 打印出上层 传输下来的 int数据,并返回 int数据。
上层 收到 native 方法 返回的 值,在UI中显示出来
public native int getNumber(int num);
jint Java_XX_XX_XXActivity_getNumber(JNIEnv* env,jobject thiz,jint num)
{
if(jniEnv == NULL) {
jniEnv = env;
}
__android_log_print(ANDROID_LOG_INFO, "JNIMsg", "Java -- > C JNI : num = %d",num);
return num*2;
}
注意:jint
与int
的互转都可以直接使用强转,如:jint i = (jint) 10;
上层定义一个native的方法,需要一个int数组,返回一个int数组;JNI 对应上层的方法,取出上层传递数组中的数据处理和打印出来,并存入新数组中,最后把该数组返回给 Java层。
上层 收到 native返回的 数组,加工成字符串,在UI中显示出来
public native int[] getArrayNumber(int[] nums);
JNIEnv* jniEnv;
jintArray Java_XX_XX_XXActivity_getArrayNumber(JNIEnv* env,jobject thiz,jintArray nums)
{
if(jniEnv == NULL) {
jniEnv = env;
}
if(nums == NULL){
return NULL;
}
jsize len = (*jniEnv)->GetArrayLength(jniEnv, nums);
if(len <= 0) {
return NULL;
}
jintArray array = (*jniEnv)->NewIntArray(jniEnv, len);
if(array == NULL) {
return NULL;
}
// 把 Java 传递下来的数组 用 jint* 存起来
jint *body = (*env)->GetIntArrayElements(env, nums, 0);
jint i = 0;
jint num[len];
for (; i < len; i++) {
num[i] = body[i] * 2;
}
if(num == NULL){
return NULL;
}
//(*env)->GetIntArrayRegion(env,array,start,len,buffer)
// 从start开始复制长度为len 的数据到buffer中
//给需要返回的数组赋值
(*jniEnv)->SetIntArrayRegion(jniEnv,array, 0, len, num);
return array;
}
对于其他类型数组,使用对应类型的成对方法读取和设置,如byte
数组可使用 NewByteArray();
和SetByteArrayRegion();
上层定义一个native的方法,需要一个String 参数,返回一个String;JNI对应上层的方法,打印出上层传输下来的String数据,并返回处理String数据。
上层 收到 native 方法 返回的 值,在UI中显示出来
public native String transferString(String mStrMSG);
jstring Java_XX_XX_XXActivity_transferString(JNIEnv* env,jobject thiz,jstring msg)
{
if(jniEnv == NULL) {
jniEnv = env;
}
char data[128];
memset(data, 0, sizeof(data));
char *c_msg = NULL;
c_msg = (char *)(*jniEnv)->GetStringUTFChars(jniEnv, msg, 0);
__android_log_print(ANDROID_LOG_INFO, "JNIMsg", "C JNI ---- > %s",c_msg);
return (*jniEnv)->NewStringUTF(jniEnv, "This is send by C JNI");
}
自定义一个对象Person,上层定义一个native方法,参数Person,返回值Person;JNI接收对象,打印出相关信息数据,JNI 修改 Person 对象数据,并返回到上层。
上层接收到数据后 在UI显示出来
public native Object transferPerson(Person mPerson);
public class Person {
private String name;
private int age;
public Person() {
name = "";
age = 0;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + "]";
}
}
extern JNIEnv* jniEnv;
jclass Person;
jobject mPerson;
jmethodID getName;
jmethodID setName;
jmethodID toString;
int InitPerson();
void ToString();
void GetName();
void SetName();
jobject Java_XX_XX_XXActivity_transferPerson(JNIEnv* env,jobject thiz,jobject person)
{
if(jniEnv == NULL) {
jniEnv = env;
}
if (Person == NULL || getName == NULL || setName == NULL || toString == NULL) {
if (1 != InitPerson()) {
return NULL;
}
}
mPerson = person;
if(mPerson == NULL) {
return NULL;
}
GetName();
GetAge();
ToString();
__android_log_print(ANDROID_LOG_INFO, "JNIMsg", "Begin Modify mPerson .... ");
SetName();
SetAge();
ToString();
return mPerson;
}
int InitPerson() {
if(jniEnv == NULL) {
return 0;
}
if(Person == NULL) {
Person = (*jniEnv)->FindClass(jniEnv,"com/XX/Person");
if(Person == NULL){
return -1;
}
}
if (getName == NULL) {
getName = (*jniEnv)->GetMethodID(jniEnv, Person, "getName","()Ljava/lang/String;");
if (getName == NULL) {
(*jniEnv)->DeleteLocalRef(jniEnv, Person);
return -2;
}
}
if (setName == NULL) {
setName = (*jniEnv)->GetMethodID(jniEnv, Person, "setName","(Ljava/lang/String;)V");
if (setName == NULL) {
(*jniEnv)->DeleteLocalRef(jniEnv, Person);
(*jniEnv)->DeleteLocalRef(jniEnv, getName);
return -2;
}
}
if (getAge == NULL) {
getAge = (*jniEnv)->GetMethodID(jniEnv, Person, "getAge","()I");
if (getAge == NULL) {
(*jniEnv)->DeleteLocalRef(jniEnv, Person);
(*jniEnv)->DeleteLocalRef(jniEnv, getName);
(*jniEnv)->DeleteLocalRef(jniEnv, setName);
return -2;
}
}
if (setAge == NULL) {
setAge = (*jniEnv)->GetMethodID(jniEnv, Person, "setAge","(I)V");
if (setAge == NULL) {
(*jniEnv)->DeleteLocalRef(jniEnv, Person);
(*jniEnv)->DeleteLocalRef(jniEnv, getName);
(*jniEnv)->DeleteLocalRef(jniEnv, setName);
(*jniEnv)->DeleteLocalRef(jniEnv, getAge);
return -2;
}
}
if (toString == NULL) {
toString = (*jniEnv)->GetMethodID(jniEnv, Person, "toString","()Ljava/lang/String;");
if (toString == NULL) {
(*jniEnv)->DeleteLocalRef(jniEnv, Person);
(*jniEnv)->DeleteLocalRef(jniEnv, getName);
(*jniEnv)->DeleteLocalRef(jniEnv, setName);
(*jniEnv)->DeleteLocalRef(jniEnv, getAge);
(*jniEnv)->DeleteLocalRef(jniEnv, setAge);
return -2;
}
}
return 1;
}
/**
* GetName 对应Person的getName方法
*/
void GetName() {
if(Person == NULL || getName == NULL) {
if(1 != InitPerson()){
return;
}
}
//调用方法
jstring jstr = (*jniEnv)->CallObjectMethod(jniEnv, mPerson, getName);
char* cstr = (char*) (*jniEnv)->GetStringUTFChars(jniEnv,jstr, 0);
__android_log_print(ANDROID_LOG_INFO, "JNIMsg", "getName ---- > %s",cstr );
//释放资源
(*jniEnv)->ReleaseStringUTFChars(jniEnv, jstr, cstr);
(*jniEnv)->DeleteLocalRef(jniEnv, jstr);
}
/**
* GetAge 对应Person的getName方法
*/
void GetAge() {
if(Person == NULL || getName == NULL) {
if(1 != InitPerson()){
return;
}
}
//调用方法
jint age = (*jniEnv)->CallIntMethod(jniEnv, mPerson, getAge);
__android_log_print(ANDROID_LOG_INFO, "JNIMsg", "getAge ---- > %d",age );
}
/**
* SetName 对应Person的setName方法
*/
void SetName() {
if(Person == NULL || setName == NULL) {
if(1 != InitPerson()){
return;
}
}
jstring jstr = (*jniEnv)->NewStringUTF(jniEnv, "Modify Name");
//调用方法
(*jniEnv)->CallVoidMethod(jniEnv, mPerson, setName,jstr);
(*jniEnv)->DeleteLocalRef(jniEnv, jstr);
}
int age = 20;
/**
* SetAge 对应Person的setAge方法
*/
void SetAge() {
if(Person == NULL || setAge == NULL) {
if(1 != InitPerson()){
return;
}
}
//调用方法
(*jniEnv)->CallVoidMethod(jniEnv, mPerson, setAge,age++);
}
/**
* ToString 对应 Person 的 toString 方法 , 打印出相关信息
*/
void ToString() {
if(Person == NULL || toString == NULL) {
if(1 != InitPerson()){
return;
}
}
jstring jstr = NULL;
char* cstr = NULL;
//调用方法
jstr = (*jniEnv)->CallObjectMethod(jniEnv, mPerson, toString);
cstr = (char*) (*jniEnv)->GetStringUTFChars(jniEnv,jstr, 0);
__android_log_print(ANDROID_LOG_INFO, "JNIMsg", "C JNI toString ---- > %s",cstr );
(*jniEnv)->ReleaseStringUTFChars(jniEnv, jstr, cstr);
(*jniEnv)->DeleteLocalRef(jniEnv, jstr);
}