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研究下,一拖再拖。真正去实现了,其实也挺简单的。