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结合全局引用,是比较合适的选择。