前言
之前兩篇主要從整體角度講解了native方法與java方法的通信以及so文件的作用麻敌,一些細(xì)節(jié)就沒有太講解詳細(xì)滓侍,可能有些朋友對其中有些還不太清晰,本文就從最基本的JNI語法帶大家熟悉下扔傅,怎么編寫native方法碰声,與java方法有哪些區(qū)別诡蜓,兩者怎么進(jìn)行對象傳輸以及調(diào)用。
本文篇幅較長胰挑,前面基礎(chǔ)知識較多蔓罚,后面直接開擼代碼,請耐心觀看~
1洽腺、JNI語法
1.1 JNIEnv 和 jobject是什么?
在native方法中脚粟,我們總會看到這兩個參數(shù),比如下面的方法
JNIEXPORT void JNICALL Java_com_jni_demo_JNIDemo_sayHello (JNIEnv * env, jobject obj)
{
cout<<"Hello World"<<endl;
}
對于JNIEnv蘸朋,指代了Java本地接口環(huán)境(Java Native Interface Environment),是一個JNI接口指針扣唱,指向了本地方法的一個函數(shù)表藕坯,該函數(shù)表中的每一個成員指向了一個JNI函數(shù)团南,本地方法通過JNI函數(shù)來訪問JVM中的數(shù)據(jù)結(jié)構(gòu),也就是通過這個JNIEnv* 指針炼彪,就可以對Java端的代碼進(jìn)行操作吐根。
對于jobject,如果native方法不是static的話辐马,這個obj就代表這個native方法的類實(shí)例拷橘,如果native方法是static的話,這個obj就代表這個native方法的類的class對象實(shí)例喜爷,也就是這個方法在哪個類里面冗疮,就代表這個類的對象實(shí)例或者class實(shí)例
1.2 JNI數(shù)據(jù)類型
眾所周知,在Java中存在2中數(shù)據(jù)類型檩帐,8種基本數(shù)據(jù)類型以及引用類型术幔,那么在JNI中也是對應(yīng)的2種數(shù)據(jù)類型,引用2張圖湃密,具體關(guān)系如下:
基本數(shù)據(jù)類型都是可以在Native層直接使用的
引用數(shù)據(jù)類型則不能直接使用诅挑,需要根據(jù)JNI函數(shù)進(jìn)行相應(yīng)的轉(zhuǎn)換后,才能使用
多維數(shù)組(包括二維數(shù)組)都是引用類型泛源,需要使用 jobjectArray 類型存取其值
1.3 域描述符
基本數(shù)據(jù)類型基本以特定的大寫字母表示
Java類 | 類型簽名 |
---|---|
int | I |
float | F |
double | D |
long | J |
boolean | Z |
byte | B |
char | C |
short | S |
一般引用類型則為 L + 該類型類描述符 + ; (注意拔妥,這兒的分號“;”只得是JNI的一部分达箍,而不是我們漢語中的分段毒嫡,下同)
例如:String類型的域描述符為 Ljava/lang/String;
對于數(shù)組,其為 : [ + 其類型的域描述符 + ;
int[ ] 其描述符為 [I
float[ ] 其描述符為 [F
String[ ] 其描述符為 [Ljava/lang/String;
Object[ ]類型的域描述符為 [Ljava/lang/Object;
多維數(shù)組則是 n個[ +該類型的域描述符 , N代表的是幾維數(shù)組幻梯。例如:
int [] []其描述符為[[I
1.4 方法操作符
將參數(shù)類型的域描述符按照申明順序放入一堆括號中跟返回值類型的域描述符兜畸, 規(guī)則如下: (參數(shù)的域描述符的疊加)返回類型描述符。 對于沒有返回值的碘梢, 用V(表示void型)
比如:String test() 對應(yīng)的就是()Ljava/lang/String; 注意";"不可忘記
? int f(int i, Object object) 對應(yīng)就是(ILjava/lang/Object;)I
依次類推咬摇,注意要仔細(xì),很容易出錯
2. JNI native方法訪問 Java
2.1 獲取方法和屬性id
上面也說過了煞躬,引用數(shù)據(jù)類型是不能直接使用肛鹏,在native層,你想直接通過java對象操作方法屬性不太現(xiàn)實(shí)恩沛,JNI在jni.h頭文件中定義了jfieldID和jmethodID類型來分別代表Java對象的屬性和方法在扰。我們在訪問或是設(shè)置Java屬性的時候,首先就要先在本地代碼取得代表該Java屬性的jfieldID雷客,然后才能在本地代碼進(jìn)行Java屬性操作
public class Person {
private int age;
private String name;
public int getAge() {
return age;
}
public void setAge(int mAge) {
age = mAge;
}
public String getName() {
return name;
}
public void setName(String mName) {
name = mName;
}
}
比如這個實(shí)體類芒珠,比如想要操作setName方法,設(shè)置一些值進(jìn)去
首先獲取到這個class對象搅裙,熟悉反射的朋友應(yīng)該一眼就看出來皱卓,大致差不多
//獲取class對象
jclass clazz_NativeTest=env->FindClass(“com/example/hik/cmake");
//獲取methodId
//第三個參數(shù)就是方法的操作符裹芝,參數(shù)是String,返回值是空娜汁,所以是(Ljava/lang/String;)V
jmethodID id_show=env->GetMethodID(clazz_NativeTest,“setName”,"(Ljava/lang/String;)V");
//同理獲取filedId也是一樣的
jfieldID jfieldID1 = env->GetFieldID(student,"name","Ljava/lang/String;")
//下面是調(diào)用方法,person是對象實(shí)例嫂易,類似反射效果
char *c_new_name = "lisi";
jstring str = env->NewStringUTF(c_new_name);
env->CallVoidMethod(person, id_show, str);
2.2本地創(chuàng)建Java對象
JNIEnv提供了下面幾個方法來創(chuàng)建一個Java對象:
jobject NewObject(jclass clazz, jmethodID methodID,...);
jobject NewObjectV(jclass clazz, jmethodIDmethodID,va_list args);
jobject NewObjectA(jclass clazz, jmethodID methodID,const jvalue *args) ;
本地創(chuàng)建Java對象的函數(shù)和前面本地調(diào)用Java方法很類似:
第一個參數(shù)jclass class 代表的你要創(chuàng)建哪個類的對象
第二個參數(shù)jmethodID methodID 代表你要使用哪個構(gòu)造方法ID來創(chuàng)建這個對象。
只要有jclass和jmethodID 掐禁,我們就可以在本地方法創(chuàng)建這個Java類的對象怜械。
指的一提的是:由于Java的構(gòu)造方法的特點(diǎn),方法名與類名一樣傅事,并且沒有返回值缕允,所以對于獲得構(gòu)造方法的ID的方法env->GetMethodID(clazz,method_name ,sig)中的第二個參數(shù)是固定為“<init>”,第三個參數(shù)和要調(diào)用的構(gòu)造方法有關(guān)享完,默認(rèn)的Java構(gòu)造方法沒有返回值灼芭,沒有參數(shù)。例如:
jclassclazz=env->FindClass("java/util/Date");
//取得java.util.Date類的jclass對象
jmethodID id_date=env->GetMethodID(clazz,"<init>","()V");
//取得某一個構(gòu)造方法的jmethodID
jobject date=env->NewObject(clazz,id_date);
//調(diào)用NewObject方法創(chuàng)建java.util.Date對象
2.3 實(shí)例代碼
2.3.1 改變Java對象屬性
public class Person {
private int age;
private String name;
public Person() {
}
public Person(int mAge, String mName) {
age = mAge;
name = mName;
}
public int getAge() {
return age;
}
public void setAge(int mAge) {
age = mAge;
}
public String getName() {
return name;
}
public void setName(String mName) {
name = mName;
}
@Override
public String toString() {
return "Person{" +
"age=" + age +
", name='" + name + '\'' +
'}';
}
}
Java層般又,我們新建一個實(shí)體類Bean彼绷,用來操作通信,然后再增加一個native方法changePersonName
public class NativeHelper {
static {
System.loadLibrary("native-lib");
}
public native String stringFromJNI();
public native int add(int a,int b);
public native void changePersonName(Person mPerson);
public native Person getStudent();
public native List<Person> getPeronList();
}
native方法茴迁,我們基于第一篇的基礎(chǔ)上寄悯,去增加一個方法
//調(diào)用java層對象,改變屬性
void changeName(JNIEnv *env, jobject instance, jobject person) {
//獲取person的class對象
jclass student = env->GetObjectClass(person);
//獲取setName方法的id
jmethodID setNameMethond = env->GetMethodID(student, "setName", "(Ljava/lang/String;)V");
char *c_new_name = "lisi";
jstring str = env->NewStringUTF(c_new_name);
//調(diào)用方法堕义,因?yàn)榉祷刂凳莢oid猜旬,所以是CallVoidMethod,再把改變后的str傳進(jìn)去
env->CallVoidMethod(person, setNameMethond, str);
}
記得在動態(tài)注冊里倦卖,把方法添加進(jìn)去
JNINativeMethod jniNativeMethod[] = {{"stringFromJNI", "()Ljava/lang/String;", (void *) backStringToJava},
{"add", "(II)I", (void *) addNum},
{"changePersonName", "(Lcom/example/taolin/jni_project/Person;)V", (void *) changeName}};
調(diào)用之后洒擦,發(fā)現(xiàn)name已經(jīng)被改變成了“l(fā)isi”,主界面代碼就不貼了怕膛,直接調(diào)用native方法就好了
2.3.2 返回Java層實(shí)體對象
我們再添加一個方法
public native Person getStudent();
native方法熟嫩,同樣的增加一個
//返回java層對象
jobject returnPerson(JNIEnv *env, jobject instance) {
//獲取到person class對象
jclass jclass1 = env->FindClass("com/example/taolin/jni_project/Person");
//獲取到構(gòu)造函數(shù)的methodId
jmethodID jmethodID1 = env->GetMethodID(jclass1, "<init>", "(ILjava/lang/String;)V");
jint age = 20;
char *back_name = "wangwu";
jstring str = env->NewStringUTF(back_name);
//NewObject,根據(jù)class對象返回一個實(shí)例對象
jobject perosn = env->NewObject(jclass1, jmethodID1, age, str);
return perosn;
}
動態(tài)注冊關(guān)聯(lián)一下
JNINativeMethod jniNativeMethod[] = {{"stringFromJNI", "()Ljava/lang/String;", (void *) backStringToJava},
{"add", "(II)I", (void *) addNum},
{"changePersonName", "(Lcom/example/taolin/jni_project/Person;)V", (void *) changeName},
{"getStudent", "()Lcom/example/taolin/jni_project/Person;", (void *) returnPerson}};
主頁面直接調(diào)用getStudent()褐捻,發(fā)現(xiàn)返回一個student對象掸茅,name為“wangwu”,native層返回對象成成功
2.3.3 native返回list對象給Java
添加一個方法
public native List<Person> getPeronList();
來柠逞,native層昧狮,對應(yīng)添加
//返回java層一個list
jobject returnList(JNIEnv *env, jobject instance) {
//因?yàn)閘ist是無法實(shí)例對象,找到Arraylist板壮,返回class對象
jclass jclass1 = env->FindClass("java/util/ArrayList");
//拿到構(gòu)造函數(shù)id
jmethodID contructMethod = env->GetMethodID(jclass1,"<init>","()V");
//生成一個Arraylist對象逗鸣,就是我們要返回的對象
jobject list = env->NewObject(jclass1,contructMethod);
//拿到 list的 add方法的methodId,準(zhǔn)備往method添加幾個數(shù)據(jù)
jmethodID methodAdd = env->GetMethodID(jclass1,"add","(Ljava/lang/Object;)Z");
//拿到Person的class對象
jclass studentClass = env->FindClass("com/example/taolin/jni_project/Person");
//拿到person的構(gòu)造函數(shù)的methodId
jmethodID jmethodID1 = env->GetMethodID(studentClass, "<init>", "(ILjava/lang/String;)V");
for(int i =0;i<4;i++){
jobject person = env->NewObject(studentClass,jmethodID1,i,env->NewStringUTF("tl"));
//調(diào)用 list的add方法,因?yàn)榉祷貢rboolean值慕购,所以CallBooleanMethod
env->CallBooleanMethod(list,methodAdd,person);
}
return list;
}
最后聊疲,注冊綁定不要忘了
JNINativeMethod jniNativeMethod[] = {{"stringFromJNI", "()Ljava/lang/String;", (void *) backStringToJava},
{"add", "(II)I", (void *) addNum},
{"changePersonName", "(Lcom/example/taolin/jni_project/Person;)V", (void *) changeName},
{"getStudent", "()Lcom/example/taolin/jni_project/Person;", (void *) returnPerson},
{"getPeronList", "()Ljava/util/List;", (void *) returnList}};
主頁面調(diào)用getPeronList()茬底,可以發(fā)現(xiàn)返回list沪悲,長度是4,調(diào)用成功~
3.總結(jié)
JNI學(xué)習(xí)就暫時告一段落了阱表,因?yàn)楸救艘彩莿偨佑|這一塊殿如,讓我講的多深,我也是心有力而與不足最爬,因?yàn)镃++學(xué)的也不是太好涉馁,所以不敢誤人子弟,但是還是希望能夠幫助到一些準(zhǔn)備入門的小伙伴來學(xué)習(xí)JNI開發(fā)爱致。
里面的坑其實(shí)還是挺多的烤送,所以小伙伴一定要自己動手去操作一下,搭一下環(huán)境糠悯,寫一些代碼帮坚,最后肯定是有所收獲的,有疑惑或者想法的朋友可以留言討論互艾,比心试和!~