低于 API 级别 26 的 NDK 和 SDK 之间的共享内存



用C++编写的库会产生连续的数据流,并且必须在不同的平台上移植相同的数据流。现在将库集成到 android 应用程序,我正在尝试在 NDK 和 SDK 之间创建共享内存。

下面是工作片段,

原生代码:

#include <jni.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <linux/ashmem.h>
#include <android/log.h>
#include <string>
char  *buffer;
constexpr size_t BufferSize=100;
extern "C" JNIEXPORT jobject JNICALL
Java_test_com_myapplication_MainActivity_getSharedBufferJNI(
JNIEnv* env,
jobject /* this */) {
int fd = open("/dev/ashmem", O_RDWR);
ioctl(fd, ASHMEM_SET_NAME, "shared_memory");
ioctl(fd, ASHMEM_SET_SIZE, BufferSize);
buffer = (char*) mmap(NULL, BufferSize, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
return (env->NewDirectByteBuffer(buffer, BufferSize));
}
extern "C" JNIEXPORT void JNICALL
Java_test_com_myapplication_MainActivity_TestBufferCopy(
JNIEnv* env,
jobject /* this */) {
for(size_t i=0;i<BufferSize;i = i+2) {
__android_log_print(ANDROID_LOG_INFO, "native_log", "Count %d value:%d", i,buffer[i]);
}
//pass `buffer` to dynamically loaded library to update share memory
//
}

开发工具包代码:

//MainActivity.java
public class MainActivity extends AppCompatActivity {
// Used to load the 'native-lib' library on application startup.
static {
System.loadLibrary("native-lib");
}
final int BufferSize = 100;
@RequiresApi(api = Build.VERSION_CODES.Q)
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ByteBuffer byteBuffer = getSharedBufferJNI();
//update the command to shared memory here
//byteBuffer updated with commands
//Call JNI to inform update and get the response
TestBufferCopy();
}

/**
* A native method that is implemented by the 'native-lib' native library,
* which is packaged with this application.
*/
public native ByteBuffer getSharedBufferJNI();
public native int TestBufferCopy();
}

问题:

  1. 仅当垃圾回收器支持固定时,才引用从 Java 到本机的基元数组。其他方式是真的吗?
  2. 安卓平台是否保证始终引用从NDK共享到SDK,没有冗余副本?
  3. 这是共享内存的正确方式吗?

您只需要/dev/ashmem在进程之间共享内存。NDK 和 SDK(Java/Kotlin( 在相同的 Linux 进程中工作,并具有对相同内存空间的完全访问权限。

定义可以从C++和Java使用的内存的常用方法是创建Direct ByteBuffer。你不需要JNI,Java API有ByteBuffer.allocateDirect(int capacity(。如果您的逻辑流更自然地在C++端分配缓冲区,JNI 具有您在问题中使用的 NewDirectByteBuffer(JNIEnv* env, void* address, jlong capacity( 函数。

使用Direct ByteBuffer在C++端非常容易,但在JVM端效率不高。原因是这个缓冲区不受数组支持,你唯一的API涉及ByteBuffer.get((和类型化变体(获取字节数组,char,int,...(。您可以控制缓冲区中的当前位置,但以这种方式工作需要一定的纪律:每个get((操作都会更新当前位置。此外,对此缓冲区的随机访问相当慢,因为它涉及调用定位和获取 API。因此,在某些非平凡数据结构的情况下,用C++编写自定义访问代码并通过 JNI 调用"智能"getter 可能会更容易。

重要的是不要忘记设置 ByteBuffer.order(ByteOrder.nativeOrder(((。新创建的字节缓冲区的顺序与直觉BIG_ENDIAN。这适用于从 Java 和C++创建的缓冲区。

如果您可以在C++需要访问此类共享内存时隔离实例,并且实际上不需要一直固定它,则值得考虑使用字节数组。在 Java 中,您可以更高效地进行随机访问。在NDK端,您将调用GetByteArrayElements((或GetPrimitiveArrayCritical((。后者更有效,但它的使用对在数组发布之前可以调用的 Java 函数施加了限制。在Android上,这两种方法都不涉及内存分配和复制(尽管没有官方保证(。即使C++端使用与 Java 相同的内存,您的 JNI 代码也必须调用相应的 Release...(( 功能,最好尽早执行此操作。通过 RAII 处理此获取/发布是一种很好的做法。

让我总结一下我的发现,

仅当垃圾回收器支持固定时,才引用从 Java 到本机的基元数组。其他方式是真的吗?

直接缓冲区的内容可能驻留在普通垃圾回收堆之外的本机内存中。因此,垃圾回收器无法声明内存。

安卓平台是否保证始终引用从NDK共享到SDK,没有冗余副本?

是的,根据NewDirectByteBuffer的文件。

jobject NewDirectByteBuffer(JNIEnv* env, void* address, jlong capacity);

分配并返回一个直接java.nio.ByteBuffer,该引用从内存地址address开始并扩展capacity字节的内存块。

最新更新