零基礎(chǔ)帶你吃掉JNI全家桶(三)

前言

之前兩篇主要從整體角度講解了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ù)類型
引用數(shù)據(jù)類型

基本數(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)境糠悯,寫一些代碼帮坚,最后肯定是有所收獲的,有疑惑或者想法的朋友可以留言討論互艾,比心试和!~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市纫普,隨后出現(xiàn)的幾起案子阅悍,更是在濱河造成了極大的恐慌,老刑警劉巖昨稼,帶你破解...
    沈念sama閱讀 218,451評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件节视,死亡現(xiàn)場離奇詭異,居然都是意外死亡假栓,警方通過查閱死者的電腦和手機(jī)寻行,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,172評論 3 394
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來但指,“玉大人寡痰,你說我怎么就攤上這事∑宓剩” “怎么了拦坠?”我有些...
    開封第一講書人閱讀 164,782評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長剩岳。 經(jīng)常有香客問我贞滨,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,709評論 1 294
  • 正文 為了忘掉前任晓铆,我火速辦了婚禮勺良,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘骄噪。我一直安慰自己尚困,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,733評論 6 392
  • 文/花漫 我一把揭開白布链蕊。 她就那樣靜靜地躺著事甜,像睡著了一般。 火紅的嫁衣襯著肌膚如雪滔韵。 梳的紋絲不亂的頭發(fā)上逻谦,一...
    開封第一講書人閱讀 51,578評論 1 305
  • 那天,我揣著相機(jī)與錄音陪蜻,去河邊找鬼邦马。 笑死,一個胖子當(dāng)著我的面吹牛宴卖,可吹牛的內(nèi)容都是我干的滋将。 我是一名探鬼主播,決...
    沈念sama閱讀 40,320評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼嘱腥,長吁一口氣:“原來是場噩夢啊……” “哼耕渴!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起齿兔,我...
    開封第一講書人閱讀 39,241評論 0 276
  • 序言:老撾萬榮一對情侶失蹤橱脸,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后分苇,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體添诉,經(jīng)...
    沈念sama閱讀 45,686評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,878評論 3 336
  • 正文 我和宋清朗相戀三年医寿,在試婚紗的時候發(fā)現(xiàn)自己被綠了栏赴。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,992評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡靖秩,死狀恐怖须眷,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情沟突,我是刑警寧澤花颗,帶...
    沈念sama閱讀 35,715評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站惠拭,受9級特大地震影響扩劝,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,336評論 3 330
  • 文/蒙蒙 一棒呛、第九天 我趴在偏房一處隱蔽的房頂上張望聂示。 院中可真熱鬧,春花似錦簇秒、人聲如沸鱼喉。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,912評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽蒲凶。三九已至气筋,卻和暖如春拆内,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背宠默。 一陣腳步聲響...
    開封第一講書人閱讀 33,040評論 1 270
  • 我被黑心中介騙來泰國打工麸恍, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人搀矫。 一個月前我還...
    沈念sama閱讀 48,173評論 3 370
  • 正文 我出身青樓抹沪,卻偏偏與公主長得像,于是被迫代替她去往敵國和親瓤球。 傳聞我的和親對象是個殘疾皇子融欧,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,947評論 2 355

推薦閱讀更多精彩內(nèi)容