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