JNI基礎之數據類型使用

數據類型

  • 基本數據類型
  • 引用數據類型

對引用數據類型的操作

  • 字符串操作
  • 數組操作
  • NIO操作
  • 訪問域
  • 調用方法
  • 域和方法描述符
字符串操作
//1. 通過char* 創建jstring
//注意當時字符數組的時候,一定要補全結束的符'\0'
char hello2[] = {'h','e','l','l','0','\0'};
char* str = env->NewStringUTF(hello2);


//2. 通過jstring 獲取的字符串 在使用完需要顯示釋放
jboolean  isCopy = JNI_FALSE;
const char * ch = env->GetStringUTFChars(content, &isCopy);
if(ch)
{
    if(JNI_TRUE==isCopy){
        LOGE("copied %s \n",ch);
    } else{
        LOGE("new %s \n",ch);
    }
}
//3. 釋放資源
env->ReleaseStringUTFChars(content,ch);
數組操作

拷貝數組的內存區域

 jfloatArray arr = env->NewFloatArray(15);
if(arr){
    LOGE("創建成功了");
}
jfloat buff[15];
jsize  size = 15;
//將java區數組複製到C數組
env->GetFloatArrayRegion(arr,0,size,buff);
for(int i=0;i<size;i++){
    buff[i] = 100;
}
//從C數組向java數組中提交修改
env->SetFloatArrayRegion(arr,0,size,buff);
return arr;

通過指針指向java數組區域

    extern "C"
JNIEXPORT void JNICALL
Java_com_canjun_ndkbeginer_MainActivity_reverseArray(JNIEnv *env, jobject thiz, jintArray grades,
                                                     jint len) {
    //通過C指針指向java數組
    jboolean  isCopy;
    jint* nGrades = env->GetIntArrayElements(grades,&isCopy);
    if(nGrades){
        if(isCopy){
            LOGE("nGrades 備份 創建成功了");
        } else{
            LOGE("nGrades 直接指向 創建成功了");
        }
    }

    for (int i = 0; i < len; ++i) {
        for(int j=i+1;j<len;++j){
            if(nGrades[i]<nGrades[j]){
                jint tmp = nGrades[j];
                nGrades[j] = nGrades[i];
                nGrades[i] = tmp;
            }
        }
    }

    // mode 有三種值
    // 0 將內容複製回去,並釋放原生數組
    // JNI_COMMIT 將內容複製回去,但是不釋放原生數組, 用於週期定更新數據
    // JNI_ABORT 不將內容複製回去,但是釋放原生數組
    jint mode = JNI_COMMIT;
    env->ReleaseIntArrayElements(grades,nGrades,mode);
}
NIO操作

原生IO在緩衝管理區,大型網絡,文件io和字符集支持方面的性能有所改進。JNI可以通過NIO實現原生與java程序之間傳遞大量數據

  1. 直接創建字節緩衝區

     // C語言創建DirectByteBuffer,並賦值數據
     extern "C"
     JNIEXPORT jobject JNICALL
     Java_com_canjun_ndkbeginer_MainActivity_obtainByteBuffer(JNIEnv *env, jobject thiz) {
         //這個數組一定要在堆中建立
         char* buffer =  (char*)malloc(sizeof(char)*10);
         for (int i = 0; i < 10; ++i) {
             buffer[i] = 'a';
         }
         return env->NewDirectByteBuffer(buffer,10);
     }
    
     //java獲取數據
     ByteBuffer b = (ByteBuffer) obtainByteBuffer();
     Log.e("新字符", b.remaining() + "");
     byte[] buffer = new byte[10];
     b.get(buffer);
     for (int i=0;i<10;i++){
         Log.e("新字符", (char) buffer[i] + "");
     }
    
  2. 直接在字節緩衝去中獲取

     //在java中定義
     ByteBuffer b = ByteBuffer.allocateDirect(1024);
     byte[] input = new byte[10];
     for (int i=0;i<10;i++){
         input[i] = 'x';
     }
     b.put(input);
     }
     initByteBuffer(b);
     
     //在C語言中獲取java中定義的byteBuffer對象
     extern "C"
     JNIEXPORT void JNICALL
     Java_com_canjun_ndkbeginer_MainActivity_initByteBuffer(JNIEnv *env, jobject thiz, jobject buffer) {
         
         char* output  = static_cast<char *>(env->GetDirectBufferAddress(buffer));
         for (int i = 0; i < 10; ++i) {
             LOGE("native %c \n",output[i]);
         }
     }
    
訪問域的操作

準備工作

public class Person {
    public static int leg = 2;
    public String name;

    public Person(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' + "leg = " + leg+ '\''+
                '}';
    }

    public String updateName(String greet){
        return greet + "," +name;
    }

    public static void thanks(String arr){
        Log.e("Person",   " thank yout very muchu " + arr);
    }

    public static void thanks(String[] arr){
        if(arr==null||arr.length!=2){
            Log.e("Person", "參數不合法");
            return;
        }
        Log.e("Person",   arr[0] + " thank yout very muchu " + arr[1]);
    }
}


//java層的調用
Person p = new Person("zfc");
//native方法
parsePerson(p);
Log.e("MainActivity",p.toString());


//native層的實現
//獲取person中的信息
extern "C"
JNIEXPORT void JNICALL
Java_com_canjun_ndkbeginer_MainActivity_parsePerson(JNIEnv *env, jobject thiz, jobject p) {
    //獲取字節碼對象
    jclass clazz = env->GetObjectClass(p);
    //獲取靜態字段信息
    jfieldID fieldId = env->GetStaticFieldID(clazz,"leg","I");
    jint leg = env->GetStaticIntField(clazz, fieldId);
    //獲取非靜態字段信息
    jfieldID nameFiledId = env->GetFieldID(clazz,"name","Ljava/lang/String;");
    if(!nameFiledId){
        LOGE("無效的nameFiledId");
        return;
    }
    jobject  name =  env->GetObjectField(p, nameFiledId);

    jboolean isCopy;
    const char * chName = env->GetStringUTFChars((jstring)name,&isCopy);
    LOGE("姓名:%s  腿:%d條",chName,leg);

    //改變對象的值
    env->SetStaticIntField(clazz,fieldId,3);
    jstring newName = env->NewStringUTF("楊戩");
    env->SetObjectField(p,nameFiledId,newName);
}
訪問方法的操作
//java層的調用
Person p = new Person("zfc");
parseMethodForPerson(p);
//native層的實現

extern "C"
JNIEXPORT void JNICALL
Java_com_canjun_ndkbeginer_MainActivity_parseMethodForPerson(JNIEnv *env, jobject thiz, jobject p) {
    //獲取字節碼對象
    jclass  clazz = env->GetObjectClass(p);
    //獲取靜態方法 public static void thanks(String[] arr)
    jmethodID m_thanks_id = env->GetStaticMethodID(clazz,"thanks","([Ljava/lang/String;)V");
    if(!m_thanks_id){
        LOGE("method thanks not found");
        return;
    }

    jclass strClazz = env->FindClass("java/lang/String");
    jobjectArray arr = env->NewObjectArray(2,strClazz,env->NewStringUTF("!"));
    env->SetObjectArrayElement(arr,1,env->NewStringUTF("zfc"));

    env->CallStaticVoidMethod(clazz,m_thanks_id,arr);
    //獲取非靜態方法 public String updateName(String greet);
    jmethodID m_updateName_id = env->GetMethodID(clazz,"updateName","(Ljava/lang/String;)Ljava/lang/String;");

    if(!m_updateName_id){
        LOGE("method updateName not found");
        return;
    }

    auto newName = (jstring)env->CallObjectMethod(p,m_updateName_id,env->NewStringUTF("zhangzihe"));
    const char* newChName = env->GetStringUTFChars(newName,0);
    LOGE("new name is %s",newChName);
    //釋放資源
    env->ReleaseStringUTFChars(newName, newChName);
}

異常處理

  • 捕獲異常
  • 拋出異常
捕獲異常
//java 層代碼 ExceptionUtil.java
public class ExceptionUtil {

    public static void callException(){
        throw new NullPointerException("1/0");
    }

    public static native void callNative();
    public static native void callNative2();
}    

//native層代碼
extern "C"
JNIEXPORT void JNICALL
Java_com_canjun_ndkbeginer_ExceptionUtil_callNative(JNIEnv *env, jclass clazz) {
    //獲取靜態方法id static void callException()
    jmethodID m_callException_id = env->GetStaticMethodID(clazz,"callException","()V");
    if(!m_callException_id){
        LOGE("method callException not found");
        return;
    }

    env->CallStaticVoidMethod(clazz,m_callException_id);
    //符合C語言特點
    jthrowable ex =  env->ExceptionOccurred();
    if(ex){
        env->ExceptionClear();
        LOGE("exception occur");
    }
}
觸發異常
extern "C"
JNIEXPORT void JNICALL
Java_com_canjun_ndkbeginer_ExceptionUtil_callNative2(JNIEnv *env, jclass clazz) {
    jclass run_clazz = env->FindClass("java/lang/RuntimeException");
    jint code = env->ThrowNew(run_clazz,"xxxxx");

}

注意此處的異常是jvm異常,native的執行順序並不會終止

全局引用 和局部引用

引用在java中扮演非常重要的角色。虛擬機通過追蹤類實例的引用,回收不再引用的垃圾,來管理對象的期限。原生代碼不再管理範疇,因此jni提供一組函數顯示地管理對象的引用及使用期間的原生代碼。JNI支持三種引用 局部引用 全局引用和 弱全局引用。

  • 局部引用
  • 全局引用
  • 若全局引用
局部引用

大多數jni函數的返回值,就是局部引用。局部引用會隨之原生方法執行結束,而自定釋放;當然也可以顯示調用進行釋放。根據jni規範,虛擬機至少運行創建16個局部引用。

//顯示刪除局部引用
env->DeleteLocalRef(clazz);

//申請更多的局部引用
env->EnsureLocalCapacity(10);
全局引用
//創建全局引用  
g_run_clzz = env->NewGlobalRef(run_clazz);
//刪除全局引用
env->DeleteGlobalRef(g_run_clzz);
全局弱引用
//創建弱全局引用
w_run_clazz = env->NewWeakGlobalRef(run_clazz);

//如果弱引用依然有效
if(!env->IsSameObject(w_run_clazz,NULL)){
    jboolean result = env->IsSameObject(w_run_clazz,NULL);
    //是否弱全局引用
    env->DeleteWeakGlobalRef(w_run_clazz);
    LOGE("weak invalid! %d \n", result);
}

線程

作爲原生環境的一部分,虛擬機支持運行原生代碼。在開發原生構件時,要記住JNI技術的一些約束。

1. 只在原生方法執行期間及正在執行原生方法的線程環境下,局部引用纔有效,局部引用不能線程間共享,但是全局引用可以。

2.被傳遞給原生方法的環境變量指針在與方法調用相關的線程也是有效的;但是不能被其他線程緩存或使用
  • 同步
  • 原生線程
同步

同步是多線成中的重要概念。與java同步類似,jni的監視器允許原生代碼利用java對象同步。虛擬機保證存取監視器的線程安全執行,而其他線程等待監視器變爲可用。

//java中的同步代碼塊
synchronized(obj){
    ...
    ...
}

//native中的同步代碼塊
 //同步代碼塊
if(JNI_OK == env->MonitorEnter(clazz){
    //錯誤處理
}

if(JNI_OK == env->MonitorExit(clazz)){
    //錯誤處理
}
//注意上面的兩個方法的調用應該成對出現,避免思索。
原生線程

因爲虛擬機不知道原生線程,所以原生線程不能java組件直接通訊。爲了能夠通訊,可以將原生線程附着到虛擬機,待執行原生線程結束後,再脫離虛擬機。

 JNIEnv *env2;
cachedJvm->AttachCurrentThread(&env2,NULL);
cachedJvm->DetachCurrentThread();
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章