前言
上一篇文章,我们讲了Ndk的基本用法即Java调用C方法返回基本类型或String类型(引用类型)。接下来,我们就要讲到C调用Java类里面的方法包括静态方法,然后用C来处理,Java代码中传入的对象,通过C代码处理过后,返回该对象。好了,话不多说,新建项目。
创建工程
通过上一篇blog介绍,大家都应该会创建项目了吧。不会的回去看看上一篇文章。这里多创建了一个MyInfo类。用于在创建对象Java与C中相互传递。如图:
实体类里面的字段有基本类型,也有引用类型(String类型)。对于NDK开发来说,接收一个MyInfo实体类对象和该对象内的引用类型属性来说,都是jobject类型。所以大家不要搞混了。
项目中H文件与C文件,根据命令和提示创建,创建的依据是,调用jni时写的方法。如下图:
这个大家一看就明白,创建JNI方法。目前Android studio 对于Ndk开发编程不能够很好的支持,所以,你会看到,即便是项目完成后成功运行,JNI方法也会标红报错。不过只要能运行,不管那么多了。再就是创建出H文件,作用类似于Java的interface(接口),C文件“实现”H文件,include进去。
创建h文件,在工程目录下我们用的是javah -jni 全类名(包名➕雷明),自动创建H文件。这里的全类名就是com.china.MainActivity。这样我们就生成了。如下图:
在MainActivity中通过提示创建出C文件,并引用(include)刚才我们生成的h文件。如下图:
接下来,就开始我们的数据操作了。
C调用Java
首先,我们要确定在C中要调用java中的什么方法。这样我们就在MainActivity中写个加法运算方法add(int x,int y)。
public int add(int x, int y) {
Toast.makeText(this, "x+y=" + (x + y), Toast.LENGTH_SHORT).show();
return x + y;
}
同理,在C中有对应的调用方法
/*
* Class: com_china_MainActivity
* Method: getInt
* Signature: (II)I
*/
JNIEXPORT jint
JNICALL Java_com_china_MainActivity_getInt
(JNIEnv *env, jobject jobj, jint ji, jint jj) {
//TODO
};
我们要使C中能够调用java,那么就需要先去调用c的方法。然后c中的方法再调用java类里面暴露给C调用的方法。(有点绕啊,��)Java->C->Java. 对应的,在java中调用 public native int getInt(int x, int y);//。然后在C代码中操作使其调用MainActivity中的add(x,y)方法。开始吧。
JNIEXPORT jint JNICALL Java_com_china_MainActivity_getInt
(JNIEnv *env, jobject jobj, jint ji, jint jj) {
jclass jclazz = (*env)->GetObjectClass(env, jobj);
// jclass jclazz = (*env)->FindClass(env, "com/china/MainActivity");
//2.得到方法
//jmethodID (*GetMethodID)(JNIEnv*, jclass, const char*, const char*);
//最后一个参数是方法签名
jmethodID jmethodIDs = (*env)->GetMethodID(env, jclazz, "add", "(II)I");
//3.实例化该类
// jobject (*AllocObject)(JNIEnv*, jclass);
// jobject jobject = (*env)->AllocObject(env, jclazz);
//4.调用方法
//jint (*CallIntMethod)(JNIEnv*, jobject, jmethodID, ...);
jint value = (*env)->CallIntMethod(env, jobj, jmethodIDs, ji, jj);
return value;
}
这里要注意的是,我们让c调用java代码,那么就要让c知道是调用哪个类的哪个方法。哪个类呢?注意
jclass jclazz = (*env)->FindClass(env, “com/china/MainActivity”); 这句话表示,获取到MainActivity类是“静态的”,我们知道,Activity是有生命周期的类,普通类是没有的。这里理解为“静态的”。所以还有一个方法,得到这个类的引用:jclass jclazz = (*env)->GetObjectClass(env, jobj); 传入的jobj就是该方法传入的jobject(类似于java中this引用之类的意思)。得到这个类后,我们还要通过操作调用到MainActivity中的add方法。
这里的操作就是获得该方法的签名。获得方法签名,我们会用到javap命令。javap -s 全类名(包名加类名)。通过GetMethodID(xx,xx,xx,xx),第一二个参数就是env(C中的指针引用),获得的java类的引用。第三个参数就是C要调用的方法名。第四个就是该方法签名。然后,就是最后一步了。调用。(CallIntMethod)(JNIEnv, jobject, jmethodID, …);方法前几个参数。都有了,后面还有个可变参数。就是我们调用java方法的传入之。这里,java与C中int等基本类型通用的。所以可以直接丢进去。完了。
简单吧。接下来更难的来了。
传递java对象
在MAinActivity中先初始化一个MyInfo对象出来,并且赋值。
private void init() {
mInfo = new MyInfo();
mInfo.setAge(20);
mInfo.setName("renk");
}
调用jni方法。 Log.e(“renk”, “age=” + getAge(mInfo));当然在点击事件里面调用了哦。在来看C中怎么操作对象,并且将对象里面的age取出来,加上100,返回给mainActivity。打印出来。
刚才,也讲了。c中是不能直接操作java对象或者类的。但是,我们可以换个思路,在C中通过该对象的类得到相应的属性。然后在C中创建一个结构体,来存传进来的对象里面的值。好,先把结构图创建出来。
当然如果不知道结构体是什么的同学,建议复习一下C知识。
typedef struct {
_Bool gen;
int ag;
char str[255];
} MyCInfo;
//创建一个结构
MyCInfo myCInfo;
创建出一个结构体后,我们就会在c方法中使用它,并且对其赋值,这里我们创建的时候,由于C没有String类型,故使用了char[]来处理字符串类型的数据。下面看具体取值、赋值、计算并返回结果的代码:
还是刚才的操作,先拿到MainActivity的对象的引用。然后,再获取传入对象的的类的相关jclass,属性id(fieldId),当然这里面同样有用到方法签名,属性签名。属性名称。。不用多做解释。主要看取值。myCInfo.gen = (env)->GetBooleanField(env, obj, jfiGen);取值是个个的来,有不同方法,这顾名思义是去boolean值得性别值。还有,GetIntField()/GetLongField()/Get…等等。要去String属性值,就用到GetObjectField(),第一二个参数不多说,第三个参数就是该属性的属性Id值。这里还要讲jstring转换为C可操作的字符串。类型为char 。通过这句话myCInfo.ag += 100;我们就实现了。将传入的年龄加上100.这边就可直接返回了。Ok。相关Get系列方法包括静态的见下图:
这里还有多说一点的,就是,怎样将C中的结构体内容赋给java对象,让其返回呢。同理如下代码。
//赋值
jobject joInfo = (*env)->AllocObject(env, jclazz);
(*env)->SetBooleanField(env, joInfo, jfiGen, myCInfo.gen);
(*env)->SetIntField(env, joInfo, jfiAG, myCInfo.ag);
jstring jstrTmp = (*env)->NewStringUTF(env, myCInfo.str);
(*env)->SetObjectField(env, joInfo, jfiNam, jstrTmp);
(*env)->SetObjectField(env, jobj, jInfofieldid, joInfo);
很好理解了,通过上面的学习,这个很容易看懂的额。先通过类,创建其对象,然后通过Set系列方法,分别赋值给该jobject对象。然后return joInfo,就over了。不多说了。
最开始讲到的还要通过C调用java静态方法,这个也很简单,就是把调用的方法CallStatic***Method系列方法,就完成了,其他都不变。相关方法下面给出:
还有一个功能就是,怎样让C去改变activity中的某个属性的值呢。哈哈,如果看了上面的东西,到这里都应该会了哈。代码如下:
/*
* Class: com_china_MainActivity
* Method: changeString
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring
JNICALL Java_com_china_MainActivity_changeString
(JNIEnv *env, jobject jobj) {
jclass jclazz = (*env)->GetObjectClass(env, jobj);
jfieldID jfieldid = (*env)->GetFieldID(env, jclazz, "mString", "Ljava/lang/String;");
jstring jstr = (jstring)(*env)->GetObjectField(env, jobj, jfieldid);
char *mString = jString2cString(env, jstr);
char *add = " add c";
strcat(mString, add);
jstring encoding = (*env)->NewStringUTF(env, mString);
(*env)->SetObjectField(env, jobj, jfieldid, encoding);
return encoding;
}
同样的通过传入的jobject,获得该jclass的引用。获取该类的属性Id(fieldID),然后转换赋值或追加都可以,随你。最后别忘啦,调用Set系列方法。就实现了我们想要的功能。
好了,讲完了,有什么不懂的额,在线留言哦 。最后贴一下,C中的代码。谢谢。
#include <jni.h>
#include <string.h>
#include "com_china_MainActivity.h"
char *jString2cString(JNIEnv *env, jstring jstr) {
char *mStr = "";
jclass jclazz = (*env)->FindClass(env, "java/lang/String");
jmethodID jmethodid = (*env)->GetMethodID(env, jclazz, "getBytes", "(Ljava/lang/String;)[B");
jstring encode = (*env)->NewStringUTF(env, "GB2312");
jbyteArray jarry = (jbyteArray)(*env)->CallObjectMethod(env, jstr, jmethodid, encode);
jsize length = (*env)->GetArrayLength(env, jarry);
jbyte *jby = (*env)->GetByteArrayElements(env, jarry, JNI_FALSE);
if (length > 0) {
mStr = (char *) malloc(length + 1);
memcpy(mStr, jby, length);
mStr[length] = 0;
}
(*env)->ReleaseByteArrayElements(env, jarry, jby, 0);
return mStr;
}
typedef struct {
_Bool gen;
int ag;
char str[255];
} MyCInfo;
MyCInfo myCInfo;
/*
* Class: com_china_MainActivity
* Method: getString
* Signature: (Lcom/china/MyInfo;)Ljava/util/List;
* public native List<String> getString(MyInfo info);
*/
JNIEXPORT void
JNICALL Java_com_china_MainActivity_getString
(JNIEnv *env, jobject jobj, jobject obj) {
MyCInfo jinfo ;
jclass jclazz = (*env)->FindClass(env,"com/china/MyInfo");
jfieldID jinfoGender = (*env)->GetFieldID(env,jclazz,"gender","Z");
jfieldID jinfoage = (*env)->GetFieldID(env,jclazz,"age","I");
jfieldID jinfoname = (*env)->GetFieldID(env,jclazz,"name","Ljava/lang/String;");
jinfo.gen = (*env)->GetBooleanField(env,obj,jinfoGender);
jinfo.ag = (*env)->GetIntField(env,obj,jinfoage);
jstring jstr = (jstring)(*env)->GetObjectField(env, obj, jinfoname);
char *str = (*env)->GetStringUTFChars(env, jstr, 0);
strcpy(jinfo.str, str);
(*env)->ReleaseStringUTFChars(env, jstr, str);
str ="";
jclass jclazz2 = (*env)->GetObjectClass(env, jobj);
jmethodID jmethodIDs = (*env)->GetMethodID(env, jclazz2, "getInfoName", "(Ljava/lang/String;)V");
(*env)->CallVoidMethod(env,jobj,jmethodIDs,jstr);
};
/*
* Class: com_china_MainActivity
* Method: getAge
* Signature: (Lcom/china/MyInfo;)I
* public native int getAge(MyInfo info);
*/
JNIEXPORT jint
JNICALL Java_com_china_MainActivity_getAge
(JNIEnv *env, jobject jobj, jobject obj) {
jclass jclaz = (*env)->GetObjectClass(env, jobj);
jclass jclazz = (*env)->FindClass(env, "com/china/MyInfo");
jfieldID jfiGen = (*env)->GetFieldID(env, jclazz, "gender", "Z");
jfieldID jfiAG = (*env)->GetFieldID(env, jclazz, "age", "I");
jfieldID jfiNam = (*env)->GetFieldID(env, jclazz, "name", "Ljava/lang/String;");
jfieldID jInfofieldid = (*env)->GetFieldID(env, jclaz, "mInfo", "Lcom/china/MyInfo;");
//取值
myCInfo.gen = (*env)->GetBooleanField(env, obj, jfiGen);
// myCInfo.ag = (*env)->GetIntField(env,obj,jfiAG);
jstring jstr = (jstring)(*env)->GetObjectField(env, obj, jfiNam);
char *str = (*env)->GetStringUTFChars(env, jstr, 0);
strcpy(myCInfo.str, str);
(*env)->ReleaseStringUTFChars(env, jstr, str);
str = "";
myCInfo.ag += 100;
//赋值
jobject joInfo = (*env)->AllocObject(env, jclazz);
(*env)->SetBooleanField(env, joInfo, jfiGen, myCInfo.gen);
(*env)->SetIntField(env, joInfo, jfiAG, myCInfo.ag);
jstring jstrTmp = (*env)->NewStringUTF(env, myCInfo.str);
(*env)->SetObjectField(env, joInfo, jfiNam, jstrTmp);
(*env)->SetObjectField(env, jobj, jInfofieldid, joInfo);
return myCInfo.ag;
};
/*
* Class: com_china_MainActivity
* Method: changeString
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring
JNICALL Java_com_china_MainActivity_changeString
(JNIEnv *env, jobject jobj) {
jclass jclazz = (*env)->GetObjectClass(env, jobj);
jfieldID jfieldid = (*env)->GetFieldID(env, jclazz, "mString", "Ljava/lang/String;");
jstring jstr = (jstring)(*env)->GetObjectField(env, jobj, jfieldid);
char *mString = jString2cString(env, jstr);
char *add = " add c";
strcat(mString, add);
jstring encoding = (*env)->NewStringUTF(env, mString);
(*env)->SetObjectField(env, jobj, jfieldid, encoding);
return encoding;
};
/*
* Class: com_china_MainActivity
* Method: changeNumber
* Signature: ()I
*/
JNIEXPORT jint
JNICALL Java_com_china_MainActivity_changeNumber
(JNIEnv *env, jobject jobj) {
jclass jclazz = (*env)->GetObjectClass(env, jobj);
jfieldID jfieldid = (*env)->GetFieldID(env, jclazz, "mNumber", "I");
jint aa = (*env)->GetIntField(env, jobj, jfieldid);
aa += 100;
(*env)->SetIntField(env, jobj, jfieldid, aa);
return aa;
};
/*
* Class: com_china_MainActivity
* Method: testGetString
* Signature: (Ljava/lang/String;)Ljava/lang/String;
* public native String testGetString(String test);
*/
JNIEXPORT jstring
JNICALL Java_com_china_MainActivity_testGetString
(JNIEnv *env, jobject jobj, jstring jstr) {
};
/*
* Class: com_china_MainActivity
* Method: getInt
* Signature: (II)I
*/
JNIEXPORT jint
JNICALL Java_com_china_MainActivity_getInt
(JNIEnv *env, jobject jobj, jint ji, jint jj) {
jclass jclazz = (*env)->GetObjectClass(env, jobj);
// jclass jclazz = (*env)->FindClass(env, "com/china/MainActivity");
//2.得到方法
//jmethodID (*GetMethodID)(JNIEnv*, jclass, const char*, const char*);
//最后一个参数是方法签名
jmethodID jmethodIDs = (*env)->GetMethodID(env, jclazz, "add", "(II)I");
//3.实例化该类
// jobject (*AllocObject)(JNIEnv*, jclass);
// jobject jobject = (*env)->AllocObject(env, jclazz);
//4.调用方法
//jint (*CallIntMethod)(JNIEnv*, jobject, jmethodID, ...);
jint value = (*env)->CallIntMethod(env, jobj, jmethodIDs, ji, jj);
return value;
}