使用DirectByteBuffer在java层和c层之间传递和交换数据

    因为业务需要,需要在java层频繁调用本地层so库中的代码,并且需要将java中的数据交给so库处理,so库处理完成之后交给java层,java层再使用。

    “传统”的做法是,在JNI中的java层将byte数组传递到c代码中,c代码使用GetByteArrayElements和ReleaseByteArrayElements的代码分别取数据和回写数据,这样的写法没有问题,一般情况下也是这么用的。但是,这种用的话会明显感觉到多了2次内存copy。因为也需要大量调用,所以想能不能再找到一种更快的方式让java层和c层代码交换数据。    

 经过研究发现,java中有一个DirectByteBuffer的类,是直接在堆外内存中开辟的空间,也就是说不是在jvm空间中的。用法是:

ByteBuffer buffer = ByteBuffer.allocateDirect(capacity)。

    如果是这样的话,那是不是可以在java中这么写,把要处理的数据放到buffer中,然后把buffer所在的内存地址传给C层,C层根据内存地址直接操作数据,C层处理完成之后只需要告诉java层处理好了,这样的话是不是就可以免去像GetByteArrayElements和ReleaseByteArrayElements做的两次内存copy了?

    那现在有个问题,就是如何获取DirectByteBuffer申请的堆外内存地址。跟踪源码发现,DirectByteBuffer中有个address()的方法可以获取到内存地址,虽然DirectByteBuffer是不可见的类,但是java中可以使用反射啊。

    于是,开始动手尝试通过java反射的方式调用address()。代码如下:

 		ByteBuffer buffer = ByteBuffer.allocateDirect(16);
        try {
            Class<?> aClass = Class.forName("java.nio.DirectByteBuffer");
            Method addressMethod = aClass.getDeclaredMethod("address");
            long address = (long)addressMethod.invoke(buffer);
            Log.e("test","address = " + address);
        } catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
            e.printStackTrace();
        }

但运行之后发现,报如下异常:

Accessing hidden method Ljava/nio/DirectByteBuffer;->address()J (blacklist,core-platform-api, reflection, denied)
W/System.err: java.lang.NoSuchMethodException: java.nio.DirectByteBuffer.address []

    我是用的Android10的手机测试,发现不能反射调用,因为从Android9开始,系统针对非系统SDK的反射加了限制。而且,不管是在java中还是在JNI中反射调用此类的方法都会失败。

    眼看这条路走不通的时候,一起开发的小伙伴“邢少”研究了之后,提供了一个在JNI中调用使用方法:

//jni之C层代码:
int useByteBuffer(JNIEnv *env, jobject thiz, jobject jbuffer) {
    void *address = env->GetDirectBufferAddress(jbuffer);//这里可以获得byteBuffer在内存中的起始位置
    ... ...
}
//jni之java层代码:
public native int useByteBuffer(ByteBuffer byteBuffer);

    通过 env->GetDirectBufferAddress(jbuffer) 来得到地址,然后就像正常操作数组一样操作jbuffer就可以了。

 

    根据此方法,写了测试demo来进行比对:使用此方式和“传统”的方式 的速度: 

    使用传递ByteBuffer的方式:

 

 “传统”的使用byte数组的方式:

     在同一台手机上测试:

     使用传递ByteBuffer的方式,用时大致是30ms多;

     “传统”的使用byte数组的方式,用时大致是70ms多;

     速率相差将近1倍。

 

    如果对 使用传递ByteBuffer的方式 在进一步优化,不用每次都GetDirectBufferAddress(jbuffer) 获取地址,而是先一步调用获取并保存address,之后一直使用这一个ByteBuffer,速度会更快:

速度大概是在15ms左右;

 

总结:

    由此可见,如果使用 DirectByteBuffer 在java层和c层之间传递和交换数据,相比“传统”的JNI写法,速度会得到一定的提升。

 

 

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值