android----在JNI中如何使用ByteBuffer

1,ByteBuffer 定义

在NIO中,数据的读写操作始终是与缓冲区相关联的(读取时信道(SocketChannel)将数据读入缓冲区,写入时首先要将发送的数据按顺序填入缓冲区)
缓冲区是定长的,基本上它只是一个列表,它的所有元素都是基本数据类型。ByteBuffer是最常用的缓冲区,它提供了读写其他数据类型的方法,且信道的读写方法只接收ByteBuffer。
ByteBuffer有以下几种常见属性:
mark:初始值为-1,标记索引地点;
position:初始值为0,索引下标;
limit:最好定义成bytebuffer的长度,即允许可读空间长度;
capacity:缓冲区能容纳的数据元素的最大数量,创建之后无法被改变;

2,ByteBuffer使用

2.1创建ByteBuffer

2.1.1使用allocate()创建:

ByteBuffer buf = ByteBuffer.allocate(length);    //length表示buf的长度

2.1.2使用数组创建:

ByteBuffer buf = ByteBuffer.wrap(byteArray);   //byteArray表示一个数组

2.2回绕缓冲区

buf.flip();  //这个方法用来将缓冲区准备为数据传出状态,执行以上方法后,输出通道会从数据的开头而不是末尾开始.回绕保持缓冲区中的数据不变,只是准备写入而不是读取。

2.3清除缓冲区

buf.clear();  //这个方法实际上也不会改变缓冲区的数据,而只是简单的重置了缓冲区的主要索引值.不必为了每次读写都创建新的缓冲区,那样做会降低性能.相反,要重用现在的缓冲区,在再次读取之前要清除缓冲区。

2.4 ByteBuffer与byte[]交互

byte[] bytearray = new byte[10];

ByteBuffer buf = ByteBuffer.wrap(bytearray);//将数组写入buf

bytearray = new byte[buf.remaining()];

buf.get(bytearray,0,bytearray.length());//将数据读到数组中

bytearray = new byte[buf.capacity()];

3 ByteBuffer与JNI交互

 在Java1.4版本中引入的JNI有三个函数可以用于NIO的直接缓冲器。一个直接字节缓冲器是一个用于字节数据的容器,Java将尽力在它上面执行本机I/O操作。JNI定义了三个用于NIO操作的函数。

/*     基于到存储器地址的指针以及存储器长度(容量),函数分配并且返回一个新的Java.nio.ByteBuffer。如果函数没有针对当前Java虚拟机实现,则返回NULL,或者抛出一个异常。如果没有存储器可用,则将会抛出一个OutOfMemoryException。*/
jobject NewDirectByteBuffer(void* address, jlong capacity);
/*   GetDirectBufferAddress函数返回一个指向被传入的java.nio.ByteBuffer对象的地址指针。如果函数尚未针对当前虚拟机实现,或者如果buf不是java.nio.ByteBuffer的一个对象,又或者存储器区尚未定义,则都将返回NULL。*/
void* GetDirectBufferAddress(jobject buf);
/*GetDirectBufferCapacity函数返回被传入的java.nio.ByteBuffer对象的容量(以字节计数)。如果函数没有针对当前环境实现,或者如果buf不是java.nio.ByteBuffer类型的对象返回-1。*/
jlong GetDirectBufferCapacity(jobject buf);
3.1Jni中调用
Java层:
 public final int processData(ByteBuffer data);
Native 接口:
 private native long native_Process(ByteBuffer data);
Jni层:
static jlong native_Process(JNIEnv *env, jobject obj,jobject data);
注意ByteBuffer在JNI层中的签名:Ljava/nio/ByteBuffer;
 
eg(C++):

jclass cls = env->GetObjectClass(obj);
 jfieldID fid = env->GetFieldID(cls, "data", "Ljava/nio/ByteBuffer;");
 jobject bar = env->GetObjectField(obj, fid);
 pImageData->data= (MByte*)env->GetDirectBufferAddress(bar);
 //data是结构体pImageData中的byte[];

 

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