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層。

Reference:
http://blog.csdn.net/xinchen200/article/details/25333047
http://zsaber.com/blog/p/107

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章