JNI中从Java往C或者C++中传递byte数组,一般有三个方法:GetByteArrayRegion、GetByteArrayElements、GetPrimitiveArrayCritical。
基本概念和用法
// GetByteArrayRegion - 直接复制特定区域
void GetData1(JNIEnv* env, jbyteArray array) {
jbyte buffer[1024];
// 显式复制,立即完成复制
env->GetByteArrayRegion(array, 0, 1024, buffer);
// 直接使用本地buffer
process(buffer);
}
// GetByteArrayElements - 获取数组引用或副本
void GetData2(JNIEnv* env, jbyteArray array) {
jboolean isCopy;
// 可能复制也可能不复制
jbyte* buffer = env->GetByteArrayElements(array, &isCopy);
if (buffer) {
process(buffer);
// 必须释放
env->ReleaseByteArrayElements(array, buffer, 0);
}
}
// GetPrimitiveArrayCritical - 获取直接引用
void GetData3(JNIEnv* env, jbyteArray array) {
jboolean isCopy;
// 尽量返回直接引用
void* buffer = env->GetPrimitiveArrayCritical(array, &isCopy);
if (buffer) {
// 快速操作
quickProcess(buffer);
// 必须尽快释放
env->ReleasePrimitiveArrayCritical(array, buffer, 0);
}
}
需要注意的mode参数选择:
- 0: 将修改内容复制回Java数组并释放缓冲区
- JNI_COMMIT: 只复制回Java数组,不释放缓冲区
- JNI_ABORT: 不复制回Java数组,只释放缓冲区
特点对比
| 特性 | GetByteArrayRegion | GetByteArrayElements | GetPrimitiveArrayCritical |
|---|---|---|---|
| 内存复制 | 总是复制 | 可能复制 | 尽量避免复制 |
| GC影响 | 无 | 允许GC | 可能禁止GC |
| 使用限制 | 无 | 较少 | 严格 |
| 性能 | 中等 | 中等 | 最好 |
| 安全性 | 最高 | 高 | 需谨慎 |
适用场景
GetByteArrayRegion:合小数据或部分数据,只需要数组部分数据、数据量较小、需要确保数据独立副本
GetByteArrayElements:适合大数据操作,需要访问完整数组、需要长时间处理、需要调用其他JNI函数
GetPrimitiveArrayCritical:适合快速访问,需要最高性能、操作简单快速、不需要调用其他函数
注意事项
- GetPrimitiveArrayCritical调用后,return到java前,必须调用ReleasePrimitiveArrayCritical,否则会报错:”JNI DETECTED ERROR IN APPLICATION: Critical lock held when returning to Java on thread Thread”。这也意味着没有办法通过GetPrimitiveArrayCritical将Java中的数组放入C++缓存,异步使用并释放。
- 由于GetByteArrayElements可能可以使用Java中的数组指针,并且也允许GC,所以有可能在使用的时候,已经被GC了,为了避免GC,可以将原jbyteArray通过全局引用保存,并在使用完成后手动调用ReleaseByteArrayElements,代码如下:
// C++端
struct BufferHolder {
JNIEnv* env;
jbyteArray array; // 全局引用
jbyte* buffer; // 实际数据指针
jsize length;
};
// JNI接口
extern "C" {
// 创建并保存引用
JNIEXPORT jlong JNICALL
Java_com_example_VideoDecoder_createBuffer(JNIEnv* env, jobject thiz, jbyteArray array) {
BufferHolder* holder = new BufferHolder();
// 创建全局引用
holder->array = (jbyteArray)env->NewGlobalRef(array);
holder->env = env;
holder->buffer = env->GetByteArrayElements(holder->array, NULL);
holder->length = env->GetArrayLength(holder->array);
return reinterpret_cast<jlong>(holder);
}
// 使用buffer
JNIEXPORT void JNICALL
Java_com_example_VideoDecoder_decode(JNIEnv* env, jobject thiz, jlong handle) {
BufferHolder* holder = reinterpret_cast<BufferHolder*>(handle);
if (holder && holder->buffer) {
// 使用holder->buffer进行解码
decodeFrame(holder->buffer, holder->length);
}
}
// 主动释放
JNIEXPORT void JNICALL
Java_com_example_VideoDecoder_releaseBuffer(JNIEnv* env, jobject thiz, jlong handle) {
BufferHolder* holder = reinterpret_cast<BufferHolder*>(handle);
if (holder) {
if (holder->buffer) {
env->ReleaseByteArrayElements(holder->array, holder->buffer, 0);
holder->buffer = nullptr;
}
if (holder->array) {
env->DeleteGlobalRef(holder->array);
holder->array = nullptr;
}
delete holder;
}
}
} // extern "C"
视频解码领域
视频解码中延迟是一个重要指标,而实现低延迟的一个重要方式就是零拷贝。同时需要兼顾播放的连续性和防止花屏,需要在C++中实现缓存。所以我认为GetByteArrayElements结合全局引用,是比较合适的选择。
Google Tip
https://developer.android.com/training/articles/perf-jni#faq:-how-do-i-share-raw-data-with-native-code
