本文转自http://blog.csdn.net/goodlixueyong/article/details/53151959
今天要讲的内容相对冷门,日常开发中的使用率较低。但是,在某些场景下,它也许可以带给我们一个新的思路,帮我们解决一些棘手的问题,因此我觉得有必要谈一谈Android中的匿名共享内存(Ashmem)。
首先来看一下什么是匿名共享内存(Ashmem)。Ashmem是一种共享内存的机制,它利用了Linux的mmap系统调用,将不同进程中的同一段物理内存映射到进程各自的虚拟地址空间,从而实现高效的进程间共享。它以驱动程序的形式实现在内核空间。它有两个特点,一是能够辅助内存管理系统来有效地管理不再使用的内存块,二是它通过Binder进程间通信机制来实现进程间的内存共享。Ashmem的两个特点就是共享和高效。共享是指可以在不同进程间共享信息,高效则是因为不同进程都是直接进行的内存操作,相对于其他的进程间通信方式来讲,这种方式会更快一些。
Android系统的匿名共享内存系统的主体是以驱动程序的形式实现在内核空间的,同时,在系统运行时库和应用程序框架层提供了访问接口,其中,在系统运行时库提供了C/C++调用接口,而在应用程序框架层提供了Java调用接口。Android开发中通常只需要调用Java接口,而实际上,应用程序框架层的Java调用接口是通过JNI方法来调用系统运行时库的C/C++调用接口,最后进入到内核空间的Ashmem驱动程序去的。
在Android中,主要提供了MemoryFile这个类来供应用使用匿名共享内存。在Android应用程序框架层,提供了一个MemoryFile接口来封装了匿名共享内存文件的创建和使用,通过JNI调用底层C++方法。
前面说到可以利用匿名共享内存来实现进程间内存共享,下面来具体讲解一下它的应用。
我们知道,使用Binder在进程间传递数据的时候,有时候会抛出TransactionTooLargeException这个异常,这个异常的产生是因为Binder驱动对内存的限制引起的。也就是说,我们不能通过Binder传递太大的数据。官方文档里有说明,最大通常限制为1M。
-
参见:https://developer.android.com/reference/android/os/TransactionTooLargeException.html
但是这个Binder的1M缓存是被当前进程中所有正在通过Binder传递数据的过程共享的,因此实际可用的大小要小于等于这个值。也就是说如果大于1M数据的话,就应该分开传。既然大数据不能通过Binder直接传递,我们就要想其他的办法。我们想到可以通过文件来跨进程传递数据,但是普通文件的效率太低,更优的方法是通过MemoryFile传递。
下面来讲一下这个方案怎么实现。考虑到进程间通信的场景,通常是Activity通过AIDL与Service通信,我们建立一个Activiy和Service,让他们分别跑在不同的进程。
在Activiy端,具体的实现代码如下:
byte[] contentBytes = new byte[100];
MemoryFile mf = new MemoryFile(“memfile”, contentBytes.length);
mf.writeBytes(contentBytes, 0, 0, contentBytes.length);
Method method = MemoryFile.class.getDeclaredMethod("getFileDescriptor");
FileDescriptor fd = (FileDescriptor) method.invoke(mf);
pfd = ParcelFileDescriptor.dup(fd);
其中MemoryFile需要两个参数,第一个是文件名,这个可以为null,第二个参数是文件长度,然后调用writeBytes将数据写入文件。由于MemoryFile的getFileDescriptor方法是@hide的,需要用反射得到文件描述符。最后调用ParcelFileDescriptor.dup()的目的是将文件描述符序列化,这是因为通过Binder传递的数据必须都是可序列化的。这样文件描述符就通过Binder传到了Service进程中。
在Service端,通过AIDL拿到文件描述符后,通过正常的文件读取方式,就可以读取到数据。
fis = new FileInputStream(fd.getFileDescriptor());
fis.read(new byte[100]);
通过这样的方式,可以避免Binder对传递的数据过大的限制,又可以解决跨进程传递数据的效率问题。