ByteBuffer使用总结
- 一、概述
- 二、DirectByteBuffer的使用方式
- 1.native创建DirectByteBuffer,java通过native映射
- 2.java层创建buffer数组,native层获取对应的native buffer
- 3.两种方式的共同点
- 三、总结
一、概述
最近做一个项目,性能不达标,在软件实现中频繁的调用jni实现java、native的数据传递,后来把数据直接放在native,减少native的调用,性能优化明显。查了下java、native的内存共享,因此,了解到java的java.nio包。在项目中主要使用的是ByteBuffer,其他类感兴趣的自行查询。
ByteBuffer内存共享分为两种:1、java栈内存共享 2、native堆申请内存。
我这里使用的是第二种方式,也即DirectByteBuffer。第一种,我的理解是java层,不同线程间方便 内存共享。由于native和java的内存不在 一个空间,为了共享,必须为一个空间,在native申请内存,IO性能 会更好,也即DirectByteBuffer。
二、DirectByteBuffer的使用方式
1.native创建DirectByteBuffer,java通过native映射
优点:封装实现
缺点:ByteBuffer一些接口不可以使用,如array(),获取数据需要调用jni接口进行拷贝
代码实现:
status_t JNJBuffer::createByteBufferFromNJBuffer(
JNIEnv *env, bool readOnly, bool clearBuffer, const sp<NJBuffer> &buffer,
jobject *buf) const {
// if this is an NJBuffer that doesn't actually hold any accessible memory,
// use a null ByteBuffer
*buf = NULL;
if (buffer == NULL) {
ALOGV("createByteBufferFromABuffer - given NULL, returning NULL");
return OK;
}
if (buffer->base() == NULL) {
ALOGV("createByteBufferFromABuffer ");
return OK;
}
jobject byteBuffer =
env->NewDirectByteBuffer(buffer->base(), buffer->capacity());
ALOGD("--------readOnly = %d",readOnly);
if (readOnly && byteBuffer != NULL) {
jobject readOnlyBuffer = env->CallObjectMethod(
byteBuffer, mByteBufferAsReadOnlyBufferMethodID);
env->DeleteLocalRef(byteBuffer);
byteBuffer = readOnlyBuffer;
}
if (byteBuffer == NULL) {
return NO_MEMORY;
}
jobject me = env->CallObjectMethod(
byteBuffer, mByteBufferOrderMethodID, mNativeByteOrderObj);
env->DeleteLocalRef(me);
me = env->CallObjectMethod(
byteBuffer, mByteBufferLimitMethodID,
clearBuffer ? buffer->capacity() : (buffer->offset() + buffer->size()));
env->DeleteLocalRef(me);
me = env->CallObjectMethod(
byteBuffer, mByteBufferPositionMethodID,
clearBuffer ? 0 : buffer->offset());
env->DeleteLocalRef(me);
me = NULL;
*buf = byteBuffer;
return OK;
}
status_t JNJBuffer::getBuffers(
JNIEnv *env, bool input, sp<NJBuffer> buffers[], size_t bufferSize, jobjectArray *bufArray) const {
if(buffers == NULL || bufferSize <= 0 || bufArray == NULL) {
return BAD_VALUE;
}
*bufArray = (jobjectArray)env->NewObjectArray(
bufferSize, mByteBufferClass, NULL);
if (*bufArray == NULL) {
return NO_MEMORY;
}
for (size_t i = 0; i < bufferSize; ++i) {
const sp<NJBuffer> &buffer = buffers[i];
jobject byteBuffer = NULL;
status_t err = createByteBufferFromNJBuffer(
env, false /* readOnly */, true /* clearBuffer */, buffer, &byteBuffer);
if (err != OK) {
return err;
}
if (byteBuffer != NULL) {
env->SetObjectArrayElement(
*bufArray, i, byteBuffer);
env->DeleteLocalRef(byteBuffer);
byteBuffer = NULL;
}
}
return OK;
}
java层调用getBuffers,获取java层映射的ByteBuffers数组,通过bufArray返回。
在java层,通过ByteBuffer的get、put等函数实现对buffer的操作。如果bu
2.java层创建buffer数组,native层获取对应的native buffer
优点:可以使用array,减少jni的调用。
缺点:buffer分配在java层,buffer大小可控性差,容易被开发修改
代码实现:
static jboolean com_icetech_randis_NetLinuxCamera_setBuffers(
JNIEnv *env, jobject thiz, jint type, jobjectArray byteBuffers, jint size) {
ALOGD("com_icetech_randis_NetLinuxCamera_setBuffers");
sp<NetLinuxCamera> nlcdev = getNetLinuxCamera(env, thiz);
if (nlcdev == NULL) {
jniThrowException(env, "java/lang/NullPointerException", NULL);
return false;
}
for(int i = 0;i < size;i++) {
jobject byteBuffer = env->GetObjectArrayElement(byteBuffers, i);
if(byteBuffer == NULL) {
jniThrowException(env, "java/lang/NullPointerException", NULL);
return false;
}
// 以下两行是重点,获取java buffer的native指针首地址及buffer的长度
// 获取buffer首地址后就可以操作buffer,读写数据
void* buffer =env->GetDirectBufferAddress(byteBuffer);
int capacity = env->GetDirectBufferCapacity(byteBuffer);
// 此处不用关心
status_t err = nlcdev->initBuffer(type, i, buffer, capacity);
if(err != OK) {
return false;
}
}
return true;
}
注意: 该方式在java层可以使用array(),但不能直接使用,需要配合arrayoffset()。在创建buffer时,为了内存地址对齐,有一定的偏移。
使用方式:
byte[] array = mCacheBuffers[currentIndex].array();
offset = mCacheBuffers[currentIndex].arrayOffset();
// 有效数据从offset开始
mCb.onVideoBufferAvailable(array, offset, length);
3.两种方式的共同点
若buffer作为循环buffer,native层要自己实现对buffer的管理;在java层,get、put后,position会自动移动,具体如何移动,没有用到没有深究。
若buffer不做为循环buffer,每次数据写完后需要执行buffer.clear(),该函数不会影响buffer中数据,只是把读写数据的位置归零,也不影响offset。
三、总结
之前开发写代码较少,一直想写代码,在写代码是思路较清晰,总是不能下手,比如使用java、native内存共享,使用arry()读取数据,java、native层数据迁移不调用jni接口,还有刚开始使用array()接口不能拿到正确数据,网上没有查到资料,需要写demo研究下,一拖再拖。真正去实现了,其实也挺简单的。