1. A brief introduction

Android Anonymous Shared Memory (Ashmem) Linux-based shared memory creates virtual files on temporary file systems (TMPFS) and maps them to different processes. It allows multiple processes to operate on the same memory area and has no size limits other than physical memory limits. Ashmem manages memory more finely than Linux’s shared memory and adds mutex. The Java layer uses memoryfiles, which encapsulate native code. The Java layer uses four points of anonymous shared memory:

1. Use MemoryFile to create memory space and obtain FileDescriptor.

2. Pass the FileDescriptor to another process.

3. Write data to the shared memory.

4. Read data from the shared memory.

The following uses an example to describe the use of anonymous shared memory. Assume that you need to create a section of shared memory, write some data, and then read this section of data in another process.

2. Create a MemoryFile and write data

/** * Data to be written to shared memory */ private val bytes = "The wind is cold." .tobytearray () /** * createMemoryFile and return ParcelFileDescriptor */ private fun createMemoryFile(): ParcelFileDescriptor? {// Create a MemoryFile object. 1024 is the maximum memory size. Val file = MemoryFile("TestAshmemFile", 1024) // Get the file descriptor because the method is labeled @hide, Val Descriptor = invokeMethod("getFileDescriptor", file) as? FileDescriptor // If it fails to get, Return if (descriptor == null) {log. I ("ZHP", "Failed to get FileDescriptor of anonymous shared memory ") return NULL} // Write data file.writebytes (bytes, 0, 0, bytes.size) to the shared memory // Because it needs to be passed across processes, Need to serialize FileDescriptor return ParcelFileDescriptor. Dup (Descriptor)} /** * Perform the obj.name() method by reflection */ private fun invokeMethod(name: String, obj: Any): Any? { val method = obj.javaClass.getDeclaredMethod(name) return method.invoke(obj) }Copy the code

Memoryfiles have two constructors, one above and the other created from an existing FileDescriptor. The size specified when a MemoryFile is created is not the actual size of the physical memory used. The actual size of the memory used is determined by the data written to it, but cannot exceed the specified size.

3. Pass the file descriptor to another process

Binder is chosen here to pass ParcelFileDescriptor. We define a Code for both ends of C/S communication to determine events:

/** * The Code that two processes use when passing FileDescriptor. */ const val MY_TRANSACT_CODE = 920511Copy the code

BindService again where needed:

Intent = intent (this, MyService::class.java) bindService(intent, serviceConnection) Context.BIND_AUTO_CREATE)Copy the code

Bind serializes file descriptors and data sizes and passes them through Binder to the Service process:

private val serviceConnection = object: ServiceConnection { override fun onServiceConnected(name: ComponentName? , binder: IBinder?) {if (binder == null) {return} // createMemoryFile and get ParcelFileDescriptor val descriptor = createMemoryFile()? : Val sendData = Parcel. Obtain () sendData.writeParcelable(descriptor, 0) sendData.writeint (bytes.size) // Save the return value of the other process val reply = Parcel. Obtain () // Start cross-process transfer binder.transact(MY_TRANSACT_CODE, SendData, reply, 0) val MSG = reply.readString() log. I ("ZHP", $MSG ")} Override Fun onServiceDisconnected(name: ComponentName? {}}Copy the code

The file descriptors for both processes point to the same file structure, and the file structure points to a memory shared area (ASMA), making the two file descriptors correspond to the same ASMA.

4. Other processes receive FileDescriptor and read data

Define a MyService to start the child process:

class MyService : Service() {

    private val binder by lazy { MyBinder() }

    override fun onBind(intent: Intent) = binder
}
Copy the code

To realize the specific MyBinder class, there are 3 steps: 1. Read the size of data stored in shared memory from serialized data in FileDescriptor; 2. Create FileInputStream according to FileDescriptor. 3. Read the data in the shared memory.

/** * Use Binder to override onTransact instead of AIDL. */ class MyBinder: Binder() {/** * file descriptors and data sizes are passed in through data. */ override fun onTransact(code: Int, data: Parcel, reply: Parcel? , flags: Int): Boolean { val parent = super.onTransact(code, data, reply, flags) if (code ! = MY_TRANSACT_CODE && code ! = 931114) {return parent} // Read ParcelFileDescriptor and convert to FileDescriptor val PFD = data.readParcelable<ParcelFileDescriptor>(javaClass.classLoader) if (pfd == null) { return parent } val descriptor = Pfd. fileDescriptor // Read the size of data in shared memory val size = data.readint () // Create InputStream val input = according to the fileDescriptor FileInputStream(Descriptor) // Reads bytes from shared memory, Val bytes = input.readbytes () val message = String(bytes, 0, size, charsets.utf_8) log. I ("ZHP", "read a String written by another process: "$message ") // Reply to the calling process reply? .writeString("Server receives FileDescriptor and reads it from shared memory: "$message" ") return true}}.Copy the code

So once you get the FileDescriptor you can not only read it but also write it, but you can also create a MemoryFile object.