當前位置:
首頁 > 知識 > Android與NativeC傳遞數據不正確問題

Android與NativeC傳遞數據不正確問題

操作系統:Windows8.1

顯卡:Nivida GTX965M

開發工具:Android studio 2.3.3

這兩天一直在調試一個BUG,具體為通過 NativeC 來處理上層Android Java傳遞的位元組數組 byte。通過查閱 Oracle手冊確認JNI 與底層 C 或者 CPP 進行交互的細節。

從Java傳遞數組到JNI層

JNI層接收Java層傳遞過來的 byte 數組,一般有兩個函數來獲取它的值,一個是通過 GetByteArrayRegin,另一個就是 GetByteArrayElements,前者是進行拷貝操作,將Java端虛擬機託管的內存數組拷貝到本地系統的數組中,後者是通過指針引用的方式,將本地系統的數組指針直接指向Java端虛擬機託管的數組對象的堆地址。由於是在移動設備上開發,出於性能的考慮選擇 GetByteArrayElements 來完成任務。

獲取位元組數組地址函數原型:

jbyte* GetByteArrayElements(jbyteArray array, jboolean* isCopy)
{ return functions->GetByteArrayElements(this, array, isCopy); }

Java調用測試代碼如下:

public native void heitaoFilter(byte[] buffer);

private void test
{
byte buffer = {0x01, 0x02, 0x03, 0x04};

heitaoFilter(buffer);

Log.d("heitaoflower", Arrays.toString(buffer));
}

NativeC具體使用的測試代碼如下:

JNIEXPORT void JNICALL
Java_io_heitao_Test_Filter(
JNIEnv *env,
jobject,
jbyteArray buffer){

int32_t buffer_size = env->GetArrayLength(buffer);

int8_t *pBuffer = env->GetByteArrayElements(buffer, NULL);

if (pBuffer != NULL)
{
for (int32_t i = 0; i < buffer_size; i++)
{
pBuffer[i] = 0;
}
}
}

調用的輸出結果如圖所示:

Android與NativeC傳遞數據不正確問題

可以觀測到 Output byte 數組依然為之前的 0x01, 0x02, 0x03, 0x04。這是為什麼呢?命名通過指針引用進行了修改,可是結果沒有變化。

通過查閱資料在 Android Official Website 關於 JNI TIPS有一段話給出了解釋,大概意思是根據不同的JVM實現 GetByteArrayElements 在運行時可能返回指針,也可能返回一份本地拷貝的指針,之前的測試程序就是因為返回了拷貝的指針。

FAQ: How do I share raw data with native code?

You may find yourself in a situation where you need to access a large buffer of raw data from both managed and native code. Common examples include manipulation of bitmaps or sound samples. There are two basic approaches.

You can store the data in a byte. This allows very fast access from managed code. On the native side, however, you"re not guaranteed to be able to access the data without having to copy it.


In some implementations, GetByteArrayElements and GetPrimitiveArrayCritical will return actual pointers to the raw data in the managed heap, but in others it will allocate a buffer on the native heap and copy the data over.

The alternative is to store the data in a direct byte buffer. These can be created with java.nio.ByteBuffer.allocateDirect, or the JNI NewDirectByteBuffer function.
Unlike regular byte buffers, the storage is not allocated on the managed heap, and can always be accessed directly from native code (get the address with GetDirectBufferAddress).
Depending on how direct byte buffer access is implemented, accessing the data from managed code can be very slow. The choice of which to use depends on two factors: Will most of the data accesses happen from code written in Java or in C/C++? If the data is eventually being passed to a system API, what form must it be in? (For example, if the data is eventually passed to a function that takes a byte[], doing processing in a direct ByteBuffer might be unwise.) If there"s no clear winner, use a direct byte buffer. Support for them is built directly into JNI, and performance should improve in future releases.

解決問題

該問題解決思路仍然是性能放在第一位,避免內存的拷貝操作,根據Android JNI 官方給出的建議使用Java的 Direct ByteBuffer配合GetDirectBufferAddress來解決問題。所謂Direct ByteBuffer簡單說就是從操作系統直接分配物理內存,而不是從JVM獲取託管的內存,如此就可以通過NativeC的代碼修改系統的內存數據了,相關的函數及修改後代碼如下:

獲取 Direct Buffer 容量函數原型:

jlong GetDirectBufferCapacity(jobject buf)
{ return functions->GetDirectBufferCapacity(this, buf); }

獲取 Direct Buffer 地址函數原型:

void* GetDirectBufferAddress(jobject buf)
{ return functions->GetDirectBufferAddress(this, buf); }

修改後的Java代碼如下:

public native void heitaoFilter(ByteBuffer buffer);

private void test
{
byte data = {0x01, 0x02, 0x03, 0x04};
ByteBuffer buffer = ByteBuffer.allocateDirect(data.length);
buffer.put(data);

heitaoFilter(buffer);
buffer.flip;
buffer.get(data);

Log.d("heitaoflower", Arrays.toString(data));
}

修改後的NativeC代碼如下:

JNIEXPORT void JNICALL
Java_io_heitao_Test_Filter(
JNIEnv *env,
jobject,
jobject buffer){

int32_t buffer_size = (int32_t)env->GetDirectBufferCapacity(buffer);

int8_t *pBuffer = (int8_t *)(env->GetDirectBufferAddress(buffer));

if (pBuffer != NULL)
{
for (int32_t i = 0; i < buffer_size; i++)
{
pBuffer[i] = 0;
}
}
}

可以觀測到 Output ByteBuffer內部系統分配的直接內存數據修改為 0x00, 0x00, 0x00, 0x00,成功修改。

Android與NativeC傳遞數據不正確問題

結論

建議使用 DirectBuffer的方式完成 Java層與NativeCode層的數據交互,雖然開發、維護的難度提升,但是避免了大量的內存分配、拷貝操作,從而帶來了大幅度的性能提升。

喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 達人科技 的精彩文章:

Kotlin + Spring Boot 請求參數驗證
JVM學習筆記一:內存管理
從零自學Hadoop(24):Impala相關操作上
JPA Advanced Mappings(映射)

TAG:達人科技 |

您可能感興趣

Galaxy S9用戶抱怨手機屏幕出現灰度顯示不準確問題
政府網站新媒體現雷人回復 專家建議明確問責制度