Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android Studio NDK 入門教程(5)--Java對象的傳遞與修改

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

編輯:關於Android編程

概述

本文主要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,"","(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對象返回。
  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved