在开发中常常会遇到从Java层传递数据到JNI层,然后在JNI拿到数据后就可以用C语言进行操作了,操作完数据后通常还需要把处理后的数据传回Java层。下面分别进行小结。
从Java层传到JNI层
- 使用GetByteArrayRegion的方式。
该方法的本质是将Java端数组数据拷贝到本地的数组中,所以在JNI对数据修改后Java端的数据并没有改变。 - 使用GetPrimitiveArrayCritical。
GetPrimitiveArrayCritical 表面上可以得到底层数据指针,在JNI层修改数组时Java层的数据也会变。But,如果只使用GetPrimitiveArrayCritical获取数据,程序运行一段时间内存会crash。所以,使用GetPrimitiveArrayCritical时必须使用ReleasePrimitiveArrayCritical ,通过测试发现当数据量大时执行ReleasePrimitiveArrayCritical会非常耗时。
从JNI层传到Java层
- 把Jni层的数组传递到Java层,一般有两种方法,一种是通过native函数的返回值来传递,另一种是通过jni层回调java层的函数来传递,后者多用于jni的线程中或是数据量较大的情况。无论哪种方法,都离不开 SetByteArrayRegion 函数,该函数将本地的数组数据拷贝到了 Java 端的数组中。
注意上面的方式中都会涉及到内存拷贝,根据实战经验,在Android系统中,一旦数据量变大,拷贝一次内存将非常耗时。所以上述方式在追求效率时不推荐使用。解决的方法可以尝试让JAVA层和JNI共享内存的方式。最后找到了两种方式。
Java层和JNI层共享内存空间
使用GetByteArrayElements方式
该方式是指针的形式,将本地的数组指针直接指向Java端的数组地址,其实本质上是JVM在堆上分配的这个数组对象上增加一个引用计数,保证垃圾回收的时候不要释放,从而交给本地的指针使用,使用完毕后指针一定要记得通过ReleaseByteArrayElements进行释放,否则会产生内存泄露。unsigned char* psrcImg = (unsigned char*)(env->GetByteArrayElements(srcImg,0)); unsigned char* pBufferI420 = (unsigned char*) (env->GetByteArrayElements(dstImg,0)); if (psrcImg == NULL || pBufferI420 == NULL) { return -1; } env->ReleaseByteArrayElements(srcImg,(jbyte*)psrcImg,0); env->ReleaseByteArrayElements(dstImg,(jbyte*)pBufferI420,0);
注意if那里最好加上,网上查了说,get那里可能失败,失败得到的psrcImg是NULL,Release的时候程序就会崩。
Direct Buffer 方式传递。
Java和Jni层的数组传递还有一个比较重要的方式,就是通过Direct Buffer来传递,这种方式类似于在堆上创建创建了一个Java和Jni层共享的整块内存区域,无论是Java层或者Jni层均可访问这块内存,并且Java端与Jni端同步变化,由于是采用的是共享内存的方式,因此相比于普通的数组传递,效率更高,但是由于构造/析构/维护这块共享内存的代价比较大,所以小数据量的数组建议还是采用上述方式,Direct Buffer方式更适合长期使用频繁访问的大块内存的共享。具体可使用GetDirectBufferAddress获得共享的内存地址。
综上,在图像算法开发中,我采用了GetByteArrayElements-ReleaseByteArrayElements的方式来传递图像数据。此外,在开发Android上的算法时,尽量避免内存拷贝,特别是JNI层。
Reference:
http://blog.csdn.net/xinchen200/article/details/25333047
http://zsaber.com/blog/p/107