編輯:關於Android編程
本文主要Java與C++之間的對象傳遞與取值。包括傳遞Java對象、返回Java對象、修改Java對象、以及性能對比。
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; } }
//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,"","(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
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","(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 3ms
C++: C++ new 5000s waste 18ms
Java: Java new 10000s waste 5ms
C++: C++ new 10000s waste 24ms
Java: Java new 50000s waste 44ms
C++: C++ new 50000s waste 121ms
Java: Java new 100000s waste 65ms
C++: C++ new 100000s waste 259ms
這次的結果表明,如果緩存方法ID和jclass能縮短一半的時間。但仍然不如Java快。這也很好理解,C++創建Java對象最終還是通過Java創建的,反復的通過反射去創建自然不如自身創建來得快。
總結
JNI中想訪問Java Object方法簽名、類名和變量名十分重要,一旦確定了就不要輕易單方面修改Java中的定義。因為這會導致JNI找不到相關的方法或類等,而引發JNI錯誤。 雖然JNI提供了各種方法來完成Java的反射操作,但是請酌情使用,因為這會讓Java代碼與C++代碼之間過度依賴。 當你需要返回C++中的結構體數據的時候,可以考慮把結構體轉換成對應的Java對象返回。
今天我們研究一下如何在Android手機上顯示GIF動態圖片 首先需要在src目錄下新建一個自定義的View,代碼如下: import a
一個簡單的畫板,可以繪制,可以選擇顏色,可以保存。 當然了這種工具一般常用的通訊軟件都是會有的,比如QQ, 飛秋等其中我們必須監聽手指的觸摸事件,手指的觸摸事件就分為3種
字符串操作JNI把Java字符串當成引用類型來處理,JNI提供了java字符串與C字符串之間相互轉換的必要函數。因為java字符串對象是不可變的,因此JNI不提供任何修改
我們在常用的電商或者旅游APP中,例如美團,手機淘寶等等,都能夠看的到有那種下拉式的二級列表菜單。具體如圖所示: 上面兩張圖就是美團的一個二級列表菜單的一個展