Android ashmem anonymous shared memory

Time:2021-12-8
catalogue
  • 1. Brief introduction
  • 2. Create memoryfile and write data
  • 3. Pass the file descriptor to other processes
  • 4. Receive filedescriptor in other processes and read data

1. Brief introduction

Android’s anonymous shared memory (ashmem) and Linux based shared memory create virtual files on the temporary file system (TMPFS) and map them to different processes. It allows multiple processes to operate on the same memory area, and there is no other size limit except the physical memory limit. Compared with Linux shared memory, ashmem manages memory more finely and adds mutex locks. When using the Java layer, you need to use memoryfile, which encapsulates the native code. The Java layer uses four points of anonymous shared memory:

1. Open up memory space through memoryfile to obtain filedescriptor;

2. Pass filedescriptor to other processes;

3. Write data to shared memory;

4. Read data from shared memory.

The following is an example to introduce the use of anonymous shared memory. Suppose you need to open up a section of shared memory, write some data, and then read this data in another process.

2. Create memoryfile and write data

/**
 *Data to be written to shared memory
 */
Private Val bytes = "the wind blows and the water is cold.". Tobytearray()


/**
 *Create a memoryfile and return the 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 marked @ hide, it can only reflect the get
    val descriptor = invokeMethod("getFileDescriptor", file) as? FileDescriptor

    //If the acquisition fails, return
    if (descriptor == null) {
        Log. I ("zhp", "failed to get filedescriptor of anonymous shared memory")
        return null
    }

    //Write data to shared memory
    file.writeBytes(bytes, 0, 0, bytes.size)

    //To pass across processes, the filedescriptor needs to be serialized
    return ParcelFileDescriptor.dup(descriptor)
}


/**
 *Execute obj. Name() method through reflection
 */
private fun invokeMethod(name: String, obj: Any): Any? {
    val method = obj.javaClass.getDeclaredMethod(name)
    return method.invoke(obj)
}

Memoryfile has two construction methods, one is based on the above, and the other is based on the existing filedescriptor. The size specified during the creation of memoryfile is not the actual physical memory size. The actual memory size is determined by the written data, but cannot exceed the specified size.

3. Pass the file descriptor to other processes

Here, choose binder to pass the parcelfiledescriptor. We define a code, which is used to determine events through communication at both ends of the C / s:

/**
 *The code used by the two processes when passing filedescriptor.
 */
const val MY_TRANSACT_CODE = 920511

Where necessary, bindservice:

//Create service process
val intent = Intent(this, MyService::class.java)
bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE)

After bind succeeds, serialize the file descriptor and data size, and then pass them to the service process through binder:

private val serviceConnection = object: ServiceConnection {

    override fun onServiceConnected(name: ComponentName?, binder: IBinder?) {
        if (binder == null) {
            return
        }

        //Create a memoryfile and get the parcelfiledescriptor
        val descriptor = createMemoryFile() ?: return

        //Pass filedescriptor and size of data in shared memory
        val sendData = Parcel.obtain()
        sendData.writeParcelable(descriptor, 0)
        sendData.writeInt(bytes.size)

        //Save the return value of the opposite process
        val reply = Parcel.obtain()

        //Start cross process delivery
        binder.transact(MY_TRANSACT_CODE, sendData, reply, 0)

        //Read the result of binder execution
        val msg = reply.readString()
        Log. I ("zhp", "binder execution result is" $MSG ")
    }

    override fun onServiceDisconnected(name: ComponentName?) {}

}

The file descriptors of the two processes point to the same file structure, which points to a memory sharing area (ASMA), so that the two file descriptors correspond to the same ASMA.

4. Receive filedescriptor in other processes and read data

First define a myservice to start the child process:


class MyService : Service() {

    private val binder by lazy { MyBinder() }

    override fun onBind(intent: Intent) = binder
}

Then implement the specific mybinder class, which mainly includes three steps: 1. Read the filedescriptor from the serialized data and the data size saved in the shared memory; 2. Create FileInputStream according to filedescriptor; 3. Read data from shared memory.

/**
 *There is no need to use Aidl here. You can inherit binder class and override ontransact.
 */
class MyBinder: Binder() {

    /**
     *The file descriptor and data size 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 from filedescriptor
        val input = FileInputStream(descriptor)

        //Reads bytes from shared memory and converts them to text
        val bytes = input.readBytes()
        val message = String(bytes, 0, size, Charsets.UTF_8)

        Log. I ("zhp", "read the string written by another process:" $message ")

        //Reply calling process
        reply?. Writestring ("server side receives filedescriptor and reads" $message "from shared memory")

        return true
    }

}

After you get the filedescriptor, you can not only read but also write data, but also create a memoryfile object.

The above is a detailed explanation of Android ashmem anonymous shared memory. For more information about Android ashmem anonymous shared memory, please pay attention to other relevant articles of developeppaer!