Android Studio NDK 入門教程(5)--Java對象的傳遞與修改

來源:互聯網
上載者:User

標籤:

概述

本文主要Java與C++之間的對象傳遞與取值。包括傳遞Java對象、返回Java對象、修改Java對象、以及效能對比。

通過JNIEnv完成資料轉換

Java對象是存在於JVM虛擬機器中的,而C++是脫離JVM而啟動並執行,如果在C++中訪問和使用Java中的對象,必然會使用JNIEnv這個橋樑。其實通過下面的代碼很容易看出,這種訪問方式和Java中的反射十分雷同。

這裡定義一個簡單Java對象用於下文測試:

package com.example.wastrel.hellojni;/** * Created by wastrel on 2016/8/24. */public class Bean {    private String msg;    private int what;    public Bean(String msg,int what)    {        this.msg=msg;        this.what=what;    }    public String getMsg() {        return msg;    }    public void setMsg(String msg) {        this.msg = msg;    }    public int getWhat() {        return what;    }    public void setWhat(int what) {        this.what = what;    }    @Override    public String toString() {        return "Msg:"+msg+";What:"+what;    }}
從C++中建立一個Java對象並返回
    //Java中的native方法聲明    public native Bean newBean(String msg,int what);
//C++中的方法實現JNIEXPORT jobject JNICALL Java_com_example_wastrel_hellojni_HelloJNI_newBean        (JNIEnv *env, jobject obj, jstring msg,jint what){    //先找到class    jclass bean_clz=env->FindClass("com/example/wastrel/hellojni/Bean");    //在實際應用中應該確保你的class、method、field存在。減少此類判斷。    if(bean_clz==NULL)    {        LOGE("can‘t find class");        return NULL;    }    //擷取建構函式。建構函式的傳回值是void,因此這裡方法簽名最後為V    jmethodID bean_init=env->GetMethodID(bean_clz,"<init>","(Ljava/lang/String;I)V");    if(bean_init==NULL)    {        LOGE("can‘t find init function");        return NULL;    }    //然後調用建構函式獲得bean    jobject bean=env->NewObject(bean_clz,bean_init,msg,what);    return bean;}

註:如果提示找不到NULL 請include<stddef.h>

C++中解析Java對象
//java方法Native聲明public native String getString(Bean bean);
//C++中的方法實現JNIEXPORT jstring JNICALL Java_com_example_wastrel_hellojni_HelloJNI_getString        (JNIEnv *env, jobject obj,jobject bean){    jclass bean_clz=env->FindClass("com/example/wastrel/hellojni/Bean");//這部分是通過get函數去擷取對應的值 //    jmethodID bean_getmsg=env->GetMethodID(bean_clz,"getMsg","()Ljava/lang/String;");//    jmethodID bean_getwhat=env->GetMethodID(bean_clz,"getWhat","()I");//    jstring jmsg=(jstring)env->CallObjectMethod(bean,bean_getmsg);//    jint what=env->CallIntMethod(bean,bean_getwhat);//這部分是通過類的成員變數直接取擷取值,你可能注意到在Java中定義的變數都是private修飾的,但在反射的調用下是毫無作用的。    jfieldID bean_fmsg=env->GetFieldID(bean_clz,"msg","Ljava/lang/String;");    jfieldID bean_fwhat=env->GetFieldID(bean_clz,"what","I");    jstring jmsg=(jstring)env->GetObjectField(bean,bean_fmsg);    jint  what=env->GetIntField(bean,bean_fwhat);//將拿到的值拼裝一個String返回回去    const char * msg=env->GetStringUTFChars(jmsg,NULL);    char *str=new char[1024];    sprintf(str,"Msg:%s;What:%d(From C++)",msg,what);    jstring rs=env->NewStringUTF(str);    delete  []str;    env->ReleaseStringUTFChars(jmsg,msg);    return rs;}

註:sprintf函數包含在stdio.h標頭檔中

C++中修改Java對象屬性值
//java方法Native聲明public native void ModifyBean(Bean bean);
//C++實現JNIEXPORT void JNICALL Java_com_example_wastrel_hellojni_HelloJNI_ModifyBean        (JNIEnv *env, jobject obj,jobject bean){    jclass bean_clz=env->FindClass("com/example/wastrel/hellojni/Bean");    jfieldID bean_fmsg=env->GetFieldID(bean_clz,"msg","Ljava/lang/String;");    jfieldID bean_fwhat=env->GetFieldID(bean_clz,"what","I");    jstring msg=env->NewStringUTF("Modify in C++");    //重新設定屬性    env->SetObjectField(bean,bean_fmsg,msg);    env->SetIntField(bean,bean_fwhat,20);    return;}
結果圖
//java中調用代碼        HelloJNI helloJNI=new HelloJNI();        Bean bean=helloJNI.newBean("This is from C++ bean",10);        tv.setText(bean.toString());        bean=new Bean("This is from Java bean",15);        tv.append("\n"+helloJNI.getString(bean));        helloJNI.ModifyBean(bean);        tv.append("\n"+bean.toString());

Java中new Object和C++中new Object的效能對比

下面我們通過一個測試函數來比較通過兩種方式的效能,這裡可以毫無疑問的告訴你,Java一定比C++的快。那麼這個對比的意義就在於,使用C++建立Java對象的時候會不會造成不可接受的卡頓。
這裡使用的測試機是華為Mate7,具體硬體設定可自行百度。
測試函數如下:

     void Test(int count)    {        long startTime=System.currentTimeMillis();        for (int i=0;i<count;i++)        {            new Bean("123",i);        }        long endTime=System.currentTimeMillis();        Log.e("Java","Java new "+count+"s waste "+(endTime-startTime)+"ms");        HelloJNI helloJNI=new HelloJNI();       startTime=System.currentTimeMillis();        for (int i=0;i<count;i++)        {            helloJNI.newBean("123",i);        }        endTime=System.currentTimeMillis();        Log.e("C++","C++ new "+count+"s waste "+(endTime-startTime)+"ms");    }

測試結果:

Java: Java new 5000s waste 3msC++: C++ new 5000s waste 38msJava: Java new 10000s waste 6msC++: C++ new 10000s waste 79msJava: Java new 50000s waste 56msC++: C++ new 50000s waste 338msJava: Java new 100000s waste 60msC++: C++ new 100000s waste 687ms

通過結果可以看出,通過C++來new對象比Java慢了足足10倍左右。但是從時間上來講,如果只是在C++中new一個Java對象,幾個微秒的時間差距完全是可以忽略不計的。

也許有人就會說,C++慢那麼多是因為每次都在FindClass,GetMethodId,而在程式運行過程中這兩個值是不會改變的。聽起來確實有這樣一個原因,下面我們將C++中的代碼稍作修改緩衝jclass和jmethodId。
修改後的newBean函數:

//用靜態變數緩衝static jclass bean_clz=NULL;static jmethodID bean_init=NULL;JNIEXPORT jobject JNICALL Java_com_example_wastrel_hellojni_HelloJNI_newBean        (JNIEnv *env, jobject obj, jstring str,jint what){    //先找到class    if(bean_clz==NULL)    {        jclass  _bean_clz=env->FindClass("com/example/wastrel/hellojni/Bean");        bean_clz=(jclass)env->NewGlobalRef(_bean_clz);    }    //擷取建構函式。建構函式的傳回值是void,因此這裡方法簽名最後為V    if(bean_init==NULL)    {        bean_init=env->GetMethodID(bean_clz,"<init>","(Ljava/lang/String;I)V");    }    //然後調用建構函式獲得bean    jobject bean=env->NewObject(bean_clz,bean_init,str,what);    return bean;}

 你可能發現了緩衝方法ID和緩衝jclass似乎不一樣。那是因為jclass其實是java.lang.Class對象,而方法ID是JNI中定義的一個結構體。如果這裡不使用env—>NewGlobalRef()函式宣告其是一個全域引用的話,在啟動並執行時候可能就會報錯:JNI ERROR (app bug): accessed stale local reference 0x5900021;表明在Jvm中該對象已經被回收了,引用已經失效了。而NewGlobalRef的作用就在於告訴JVM,C++中一直持有該引用,請不要回收。顯然這又引發了另外一個問題,你需要在你不需要該引用的時候告訴JVM,那麼就需要調用env->DelGlobalRef()。當然你也可以不調用,那麼該Java對象將在你的程式關閉的時候被回收。

測試結果:

Java: Java new 5000s waste 3msC++: C++ new 5000s waste 18msJava: Java new 10000s waste 5msC++: C++ new 10000s waste 24msJava: Java new 50000s waste 44msC++: C++ new 50000s waste 121msJava: Java new 100000s waste 65msC++: C++ new 100000s waste 259ms

這次的結果表明,如果緩衝方法ID和jclass能縮短一半的時間。但仍然不如Java快。這也很好理解,C++建立Java對象最終還是通過Java建立的,反覆的通過反射去建立自然不如自身建立來得快。

總結
  • JNI中想訪問Java Object方法簽名、類名和變數名十分重要,一旦確定了就不要輕易單方面修改Java中的定義。因為這會導致JNI找不到相關的方法或類等,而引發JNI錯誤。
  • 雖然JNI提供了各種方法來完成Java的反射操作,但是請酌情使用,因為這會讓Java代碼與C++代碼之間過度依賴。
  • 當你需要返回C++中的結構體資料的時候,可以考慮把結構體轉換成對應的Java對象返回。

Android Studio NDK 入門教程(5)--Java對象的傳遞與修改

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.