3

用 C++ 编写的库会产生连续的数据流,并且必须将其移植到不同的平台上。现在将 lib 集成到 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. android平台是否保证总是从NDK共享参考到SDK而没有冗余副本?
  3. 这是共享内存的正确方法吗?
4

2 回答 2

2

您只需要/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)函数。

在 C++ 端使用 Direct ByteBuffer 非常容易,但在 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 处理此 Get/Release 是一种很好的做法。

于 2020-02-20T09:49:36.520 回答
0

让我总结一下我的发现,

仅当垃圾收集器支持固定时,才可以参考从 Java 到本机的原始数组。其他方式是真的吗?

直接缓冲区的内容可能存在于普通垃圾收集堆之外的本机内存中。因此垃圾收集器不能申请内存。

android平台是否保证总是从NDK共享参考到SDK而没有冗余副本?

是的,根据NewDirectByteBuffer.

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

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

于 2020-02-14T09:40:46.150 回答