1. Brief description
Android's Anonymous Shared Memory (Ashmem) Linux-based shared memory is created on the temporary file system (tmpfs) and then mapped to different processes. It allows multiple processes to operate the same memory area, and there are no other size limits except physical memory limits. Compared with Linux's shared memory, Ashmem manages memory more granularly and adds mutex locks. The Java layer needs to use MemoryFile when using it, which encapsulates native code. The Java layer uses 4 points of anonymous shared memory:
1. Open up memory space through MemoryFile and 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 piece of shared memory, write some data, and then read this piece of data in another process.
2. Create MemoryFile and Data Write
/** * Data that needs to be written to shared memory */ private val bytes = "The wind is whistling and the water is cold.".toByteArray() /** * Create MemoryFile and return ParcelFileDescriptor */ private fun createMemoryFile(): ParcelFileDescriptor? { // Create a MemoryFile object, 1024 is the maximum memory occupancy. val file = MemoryFile("TestAshmemFile", 1024) // Get the file descriptor, because the method is marked as @hide, it can only be retrieved in reflection val descriptor = invokeMethod("getFileDescriptor", file) as? FileDescriptor // If the acquisition fails, return if (descriptor == null) { ("ZHP", "Failed to obtain FileDescriptor for anonymous shared memory") return null } // Write data into shared memory (bytes, 0, 0, ) // Because it is necessary to pass across processes, it is necessary to serialize FileDescriptor return (descriptor) } /** * Execute () method through reflection */ private fun invokeMethod(name: String, obj: Any): Any? { val method = (name) return (obj) }
MemoryFile has two constructors, one above, and the other is created based on the existing FileDescriptor. The size specified when MemoryFile is created is not the actual physical memory size occupied. The actual memory size is determined by the written data, but cannot exceed the specified size.
3. Pass the file descriptor to another process
Here you choose to use Binder to pass ParcelFileDescriptor. We define a Code for communication to determine events on both ends of C/S:
/** * Code used by both processes when passing FileDescriptor. */ const val MY_TRANSACT_CODE = 920511
Then bindService where needed:
// Create a service processval intent = Intent(this, MyService::) bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE)
After successful bind, serializes the file descriptor and data size, and passes it to the Service process through Binder:
private val serviceConnection = object: ServiceConnection { override fun onServiceConnected(name: ComponentName?, binder: IBinder?) { if (binder == null) { return } // Create MemoryFile and get ParcelFileDescriptor val descriptor = createMemoryFile() ?: return // Pass FileDescriptor and the size of data in shared memory val sendData = () (descriptor, 0) () // Save the return value of the other party's process val reply = () // Start cross-process delivery (MY_TRANSACT_CODE, sendData, reply, 0) // Read the results of Binder execution val msg = () ("ZHP", "Binder execution result is: "$msg"") } override fun onServiceDisconnected(name: ComponentName?) {} }
The file descriptors of the two processes point to the same file structure, and the file structure 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 open the child process:
class MyService : Service() { private val binder by lazy { MyBinder() } override fun onBind(intent: Intent) = binder }
Implementing the specific MyBinder class mainly includes 3 steps: 1. Read the data size saved in FileDescriptor and shared memory from the serialized data; 2. Create FileInputStream based on FileDescriptor; 3. Read the data in shared memory.
/** * You don’t need to use AIDL here, just inherit the Binder class and overwrite onTransact. */ class MyBinder: Binder() { /** * File descriptor and data size are passed in via data. */ override fun onTransact(code: Int, data: Parcel, reply: Parcel?, flags: Int): Boolean { val parent = (code, data, reply, flags) if (code != MY_TRANSACT_CODE && code != 931114) { return parent } // Read ParcelFileDescriptor and convert it to FileDescriptor val pfd = <ParcelFileDescriptor>() if (pfd == null) { return parent } val descriptor = // Read the size of data in shared memory val size = () // Create InputStream based on FileDescriptor val input = FileInputStream(descriptor) // Read bytes from shared memory and convert them to text val bytes = () val message = String(bytes, 0, size, Charsets.UTF_8) ("ZHP", "Read to the string written by another process: "$message"") // Reply to call the process reply?.writeString("The Server side received FileDescriptor and read from shared memory: "$message"") return true } }
After getting FileDescriptor here, you can not only read and write data, but also create a MemoryFile object.
The above is a detailed explanation of the anonymous shared memory of Android Ashmem. For more information about Android Ashmem anonymous shared memory, please follow my other related articles!