1. 程式人生 > >Android Java與JNI層互傳資料總結

Android Java與JNI層互傳資料總結

在開發中常常會遇到從Java層傳遞資料到JNI層,然後在JNI拿到資料後就可以用C語言進行操作了,操作完資料後通常還需要把處理後的資料傳回Java層。下面分別進行小結。

從Java層傳到JNI層

  • 使用GetByteArrayRegion的方式
    該方法的本質是將Java端陣列資料拷貝到本地的陣列中,所以在JNI對資料修改後Java端的資料並沒有改變。
  • 使用GetPrimitiveArrayCritical
    GetPrimitiveArrayCritical 表面上可以得到底層資料指標,在JNI層修改陣列時Java層的資料也會變。But,如果只使用GetPrimitiveArrayCritical獲取資料,程式執行一段時間記憶體會crash。所以,使用GetPrimitiveArrayCritical時必須使用ReleasePrimitiveArrayCritical ,通過測試發現當資料量大時執行ReleasePrimitiveArrayCritical會非常耗時。

從JNI層傳到Java層

  • 把Jni層的陣列傳遞到Java層,一般有兩種方法,一種是通過native函式的返回值來傳遞,另一種是通過jni層回撥java層的函式來傳遞,後者多用於jni的執行緒中或是資料量較大的情況。無論哪種方法,都離不開 SetByteArrayRegion 函式,該函式將本地的陣列資料拷貝到了 Java 端的陣列中。

注意上面的方式中都會涉及到記憶體拷貝,根據實戰經驗,在Android系統中,一旦資料量變大,拷貝一次記憶體將非常耗時。所以上述方式在追求效率時不推薦使用。解決的方法可以嘗試讓JAVA層和JNI共享記憶體的方式。最後找到了兩種方式。

Java層和JNI層共享記憶體空間

  • 使用GetByteArrayElements方式
    該方式是指標的形式,將本地的陣列指標直接指向Java端的陣列地址,其實本質上是JVM在堆上分配的這個陣列物件上增加一個引用計數,保證垃圾回收的時候不要釋放,從而交給本地的指標使用,使用完畢後指標一定要記得通過ReleaseByteArrayElements進行釋放,否則會產生記憶體洩露。

    unsigned char* psrcImg = (unsigned char*)(env->GetByteArrayElements(srcImg,0));  
    unsigned char* pBufferI420 = (unsigned char
    *) (env->GetByteArrayElements(dstImg,0)); if (psrcImg == NULL || pBufferI420 == NULL) { return -1; } env->ReleaseByteArrayElements(srcImg,(jbyte*)psrcImg,0); env->ReleaseByteArrayElements(dstImg,(jbyte*)pBufferI420,0);

    注意if那裡最好加上,網上查了說,get那裡可能失敗,失敗得到的psrcImg是NULL,Release的時候程式就會崩。

  • Direct Buffer 方式傳遞。
    Java和Jni層的陣列傳遞還有一個比較重要的方式,就是通過Direct Buffer來傳遞,這種方式類似於在堆上建立建立了一個Java和Jni層共享的整塊記憶體區域,無論是Java層或者Jni層均可訪問這塊記憶體,並且Java端與Jni端同步變化,由於是採用的是共享記憶體的方式,因此相比於普通的陣列傳遞,效率更高,但是由於構造/析構/維護這塊共享記憶體的代價比較大,所以小資料量的陣列建議還是採用上述方式,Direct Buffer方式更適合長期使用頻繁訪問的大塊記憶體的共享。具體可使用GetDirectBufferAddress獲得共享的記憶體地址。

綜上,在影象演算法開發中,我採用了GetByteArrayElements-ReleaseByteArrayElements的方式來傳遞影象資料。此外,在開發Android上的演算法時,儘量避免記憶體拷貝,特別是JNI層。