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

前言

以前兩篇主要從總體角度講解了native方法與java方法的通訊以及so文件的做用,一些細節就沒有太講解詳細,可能有些朋友對其中有些還不太清晰,本文就從最基本的JNI語法帶你們熟悉下,怎麼編寫native方法,與java方法有哪些區別,二者怎麼進行對象傳輸以及調用。java

零基礎帶你吃掉JNI全家桶(一)數組

零基礎帶你吃掉JNI全家桶(二)bash

本文篇幅較長,前面基礎知識較多,後面直接開擼代碼,請耐心觀看~數據結構

一、JNI語法

1.1 JNIEnv 和 jobject是什麼?

在native方法中,咱們總會看到這兩個參數,好比下面的方法ide

JNIEXPORT void JNICALL Java_com_jni_demo_JNIDemo_sayHello (JNIEnv * env, jobject obj)
{
cout<<"Hello World"<<endl;
}
複製代碼

對於JNIEnv,指代了Java本地接口環境(Java Native Interface Environment),是一個JNI接口指針,指向了本地方法的一個函數表,該函數表中的每個成員指向了一個JNI函數,本地方法經過JNI函數來訪問JVM中的數據結構,也就是經過這個JNIEnv* 指針,就能夠對Java端的代碼進行操做。函數

對於jobject,若是native方法不是static的話,這個obj就表明這個native方法的類實例,若是native方法是static的話,這個obj就表明這個native方法的類的class對象實例,也就是這個方法在哪一個類裏面,就表明這個類的對象實例或者class實例post

1.2 JNI數據類型

衆所周知,在Java中存在2中數據類型,8種基本數據類型以及引用類型,那麼在JNI中也是對應的2種數據類型,引用2張圖,具體關係以下:學習

基本數據類型

引用數據類型

基本數據類型都是能夠在Native層直接使用的ui

引用數據類型則不能直接使用,須要根據JNI函數進行相應的轉換後,才能使用spa

多維數組(包括二維數組)都是引用類型,須要使用 jobjectArray 類型存取其值

1.3 域描述符

基本數據類型基本以特定的大寫字母表示

Java類 類型簽名
int I
float F
double D
long J
boolean Z
byte B
char C
short S

通常引用類型則爲 L + 該類型類描述符 + ; (注意,這兒的分號「;」只得是JNI的一部分,而不是咱們漢語中的分段,下同) 例如:String類型的域描述符爲 Ljava/lang/String; 對於數組,其爲 : [ + 其類型的域描述符 + ; int[ ] 其描述符爲 [I float[ ] 其描述符爲 [F String[ ] 其描述符爲 [Ljava/lang/String; Object[ ]類型的域描述符爲 [Ljava/lang/Object; 多維數組則是 n個[ +該類型的域描述符 , N表明的是幾維數組。例如: int [] []其描述符爲[[I

1.4 方法操做符

將參數類型的域描述符按照申明順序放入一堆括號中跟返回值類型的域描述符, 規則以下: (參數的域描述符的疊加)返回類型描述符。 對於沒有返回值的, 用V(表示void型)

好比:String test() 對應的就是()Ljava/lang/String; 注意";"不可忘記

​ int f(int i, Object object) 對應就是(ILjava/lang/Object;)I

依次類推,注意要仔細,很容易出錯

2. JNI native方法訪問 Java

2.1 獲取方法和屬性id

上面也說過了,引用數據類型是不能直接使用,在native層,你想直接經過java對象操做方法屬性不太現實,JNI在jni.h頭文件中定義了jfieldID和jmethodID類型來分別表明Java對象的屬性和方法。咱們在訪問或是設置Java屬性的時候,首先就要先在本地代碼取得表明該Java屬性的jfieldID,而後才能在本地代碼進行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;
    }
}
複製代碼

好比這個實體類,好比想要操做setName方法,設置一些值進去

首先獲取到這個class對象,熟悉反射的朋友應該一眼就看出來,大體差很少

//獲取class對象

jclass clazz_NativeTest=env->FindClass(「com/example/hik/cmake"); //獲取methodId //第三個參數就是方法的操做符,參數是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;") //下面是調用方法,person是對象實例,相似反射效果 char *c_new_name = "lisi"; jstring str = env->NewStringUTF(c_new_name); env->CallVoidMethod(person, id_show, str); 複製代碼

2.2本地建立Java對象

JNIEnv提供了下面幾個方法來建立一個Java對象:

jobject NewObject(jclass clazz, jmethodID methodID,...);

jobject NewObjectV(jclass clazz, jmethodIDmethodID,va_list args);

jobject NewObjectA(jclass clazz, jmethodID methodID,const jvalue *args) ;

本地建立Java對象的函數和前面本地調用Java方法很相似:

第一個參數jclass class 表明的你要建立哪一個類的對象

第二個參數jmethodID methodID 表明你要使用哪一個構造方法ID來建立這個對象。

只要有jclass和jmethodID ,咱們就能夠在本地方法建立這個Java類的對象。

指的一提的是:因爲Java的構造方法的特色,方法名與類名同樣,而且沒有返回值,因此對於得到構造方法的ID的方法env->GetMethodID(clazz,method_name ,sig)中的第二個參數是固定爲「」,第三個參數和要調用的構造方法有關,默認的Java構造方法沒有返回值,沒有參數。例如:

jclassclazz=env->FindClass("java/util/Date");                                   
//取得java.util.Date類的jclass對象
jmethodID id_date=env->GetMethodID(clazz,"<init>","()V");    
//取得某一個構造方法的jmethodID
jobject date=env->NewObject(clazz,id_date);                            
 //調用NewObject方法建立java.util.Date對象
複製代碼

2.3 實例代碼

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層,咱們新建一個實體類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方法,咱們基於第一篇的基礎上,去增長一個方法

//調用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);
    //調用方法,由於返回值是void,因此是CallVoidMethod,再把改變後的str傳進去
    env->CallVoidMethod(person, setNameMethond, str);
}
複製代碼

記得在動態註冊裏,把方法添加進去

JNINativeMethod jniNativeMethod[] = {{"stringFromJNI",    "()Ljava/lang/String;",                       (void *) backStringToJava},
                                         {"add",              "(II)I",                                      (void *) addNum},
                                         {"changePersonName", "(Lcom/example/taolin/jni_project/Person;)V", (void *) changeName}};
複製代碼

調用以後,發現name已經被改變成了「lisi」,主界面代碼就不貼了,直接調用native方法就行了

2.3.2 返回Java層實體對象

咱們再添加一個方法

public native Person getStudent();
複製代碼

native方法,一樣的增長一個

//返回java層對象
jobject returnPerson(JNIEnv *env, jobject instance) {
	//獲取到person class對象
    jclass jclass1 = env->FindClass("com/example/taolin/jni_project/Person");
    //獲取到構造函數的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,根據class對象返回一個實例對象
    jobject perosn = env->NewObject(jclass1, jmethodID1, age, str);
    return perosn;
}
複製代碼

動態註冊關聯一下

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}};
複製代碼

主頁面直接調用getStudent(),發現返回一個student對象,name爲「wangwu」,native層返回對象成成功

2.3.3 native返回list對象給Java

添加一個方法

public native List<Person> getPeronList();
複製代碼

來,native層,對應添加

//返回java層一個list
jobject returnList(JNIEnv *env, jobject instance) {
	//由於list是沒法實例對象,找到Arraylist,返回class對象
    jclass jclass1 = env->FindClass("java/util/ArrayList");
    //拿到構造函數id
    jmethodID contructMethod = env->GetMethodID(jclass1,"<init>","()V");
    //生成一個Arraylist對象,就是咱們要返回的對象
    jobject list = env->NewObject(jclass1,contructMethod);
    //拿到 list的 add方法的methodId,準備往method添加幾個數據
    jmethodID methodAdd = env->GetMethodID(jclass1,"add","(Ljava/lang/Object;)Z");
    //拿到Person的class對象
    jclass studentClass = env->FindClass("com/example/taolin/jni_project/Person");
    //拿到person的構造函數的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"));
        //調用 list的add方法,由於返回時boolean值,因此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}};
複製代碼

主頁面調用getPeronList(),能夠發現返回list,長度是4,調用成功~

3.總結

JNI學習就暫時告一段落了,由於本人也是剛接觸這一塊,讓我講的多深,我也是心有力而與不足,由於C++學的也不是太好,因此不敢誤人子弟,可是仍是但願可以幫助到一些準備入門的小夥伴來學習JNI開發。

裏面的坑其實仍是挺多的,因此小夥伴必定要本身動手去操做一下,搭一下環境,寫一些代碼,最後確定是有所收穫的,有疑惑或者想法的朋友能夠留言討論,比心!~

相關文章
相關標籤/搜索