Android匿名共享内存突破Binder传递大小限制
一、Binder 传输限制
在 Android 中,Activity 之间通信(无论是通过 Intent 的 extras 还是其他基于 Binder 的 IPC)底层都依赖于 Binder 驱动。Binder 被优化用于传输小型、高频的控制命令,而不是大数据块。
-
限制大小: 在一次 Binder 事务(Transaction)中,可以传输的数据量有一个严格的限制。这个限制因 Android 版本和设备而异,但通常约为 1MB(准确值是 1024*1024 - 4096 字节,即 1MB 减去一个页面的大小)。
-
后果: 如果你尝试通过 Intent.putExtra() 传递一个超过这个限制的 Bitmap 或 Serializable 对象,系统会抛出 TransactionTooLargeException 异常。
二、解决方案:匿名共享内存
Ashmem 是 Linux 内核提供的一个机制,Android 系统利用它来实现进程间共享内存区域。它的核心思想是:
-
不复制数据: 在进程间传递数据时,不再需要将整个数据块从一个进程的用户空间拷贝到内核空间,再拷贝到另一个进程的用户空间(这是 Binder 的工作方式)。
-
共享内存区域: 创建一块内存区域,多个进程可以同时映射到自己的虚拟地址空间并直接访问。数据只存在一份。
三、Android 如何利用 Ashmem 来传递大数据?
系统级别的类(如 Bitmap, Bundle, Parcel)在内部智能地使用了 Ashmem。当需要传输的数据很大时,流程如下:
-
创建 Ashmem 区域: 发送方将大数据(例如一个大 Bitmap 的像素数据)放入一块 Ashmem 中。
-
传递文件描述符: Ashmem 区域由一个文件描述符(fd)来标识。Binder 非常擅长传递这种小的文件描述符,它可以轻松地穿过 1MB 的限制。
-
接收方映射内存: 接收方进程通过 Binder 收到这个文件描述符后,将其映射到自己的进程空间。这样,两个进程就可以访问同一块物理内存了。
四、unmarshall 的角色
现在我们来谈谈 unmarshall。这个方法属于 Parcelable 协议的一部分。
- Parcel: 是一个用于序列化和反序列化的容器,它专门为高性能的 IPC 传输而设计。
- Parcelable: 是一个接口,对象实现它之后就可以被“打平”放入 Parcel 中进行传输。
- writeToParcel(Parcel dest, int flags): 对象将自己序列化到 Parcel 中。
- unmarshall(byte[] data, int offset, int length): 这是一个静态方法,用于将字节数组反序列化,还原成一个 Parcel 对象。
unmarshall 本身并不直接“突破”大小限制。 它的作用是在数据传输的“接收端”进行数据重组。
关键点在于: 如果你直接将大数据写入 Parcel,而 Parcel 的底层实现检测到数据量很大,它会自动选择使用 Ashmem 来存储数据。所以,整个流程是:
- 发送方: 将你的大对象(例如一个实现了 Parcelable 的包含大数组的类)写入 Parcel。
- 系统底层: Parcel 内部使用 Ashmem 来存储实际的大数据。
- 传输: 只有 Ashmem 的文件描述符和小量的元数据通过 Binder 传输。
- 接收方: 收到一个包含数据的 Bundle 或 Intent,其底层是一个 Parcel。
- 使用 unmarshall(通常是间接的): 当你调用 Intent.getParcelableExtra() 或 Bundle.getParcelable() 时,系统内部会从接收到的字节数据中调用 Parcel.unmarshall 或其他相关方法,重建出 Parcel 对象,然后再从 Parcel 中反序列化出你的 Java 对象。如果数据存储在 Ashmem 中,unmarshall 过程会正确地映射那块共享内存。
五、实践示例:自己使用 Ashmem
- AshmemoryTools
/*** 匿名共享内存*/
object AshmemoryTools {private val root_path = AppUtils.getContext().cacheDir.absolutePathprivate val map = ConcurrentHashMap<String, MemoryFile?>()private val sourceMap = ConcurrentHashMap<String, HashMap<String, List<Parcelable>?>>()/*** 写入数据*/fun write(data: HashMap<String, List<Parcelable>?>): String {val path = root_path + System.currentTimeMillis()val parcel = Parcel.obtain().apply {writeInt(data.size)// 写入每个键值对data.forEach { (key, valueList) ->writeString(key)// 写入列表大小writeInt(valueList?.size?:0)// 写入列表中的每个Parcelable对象valueList?.forEach { parcelable ->writeParcelable(parcelable, 0)}}}try {val bytes = parcel.marshall()map[path] = MemoryFile(path, bytes.size).apply {writeBytes(bytes, 0, 0, bytes.size)}} catch (ex: Exception) {ex.printStackTrace()sourceMap[path] = data} finally {parcel.recycle()}return path}/*** 读出数据*/fun read(path: String,classLoader: ClassLoader): HashMap<String, List<Parcelable>?>? {val memoryFile = map[path]try {if (memoryFile != null) {val bytes = ByteArray(memoryFile.length()).also {memoryFile.readBytes(it, 0, 0, it.size)}val parcel = Parcel.obtain()parcel.unmarshall(bytes, 0, bytes.size)parcel.setDataPosition(0)val size = parcel.readInt()return HashMap<String, List<Parcelable>?>(size).apply {repeat(size) {val key = parcel.readString()!!val listSize = parcel.readInt()val valueList = ArrayList<Parcelable>(listSize).apply {repeat(listSize) {add(parcel.readParcelable<Parcelable>(classLoader)!!)}}put(key, valueList)}}}} catch (ex: Exception) {ex.printStackTrace()} finally {memoryFile?.close()}return sourceMap.get(path)}/*** 释放匿名共享内存*/fun delete(path: String) {try {val memoryFile = map[path]memoryFile?.close()sourceMap.clear()} catch (ex: Exception) {ex.printStackTrace()} finally {map.remove(path)}}
}
- AshmemCache
const val ASHMEM_CACHE_KEY = "ashmem_cache_key"
class AshmemCache() {val dataMap = HashMap<String, List<Parcelable>?>()/*** 加入数据*/fun put(key:String,value:List<Parcelable>?):AshmemCache{dataMap[key] = valuereturn this}/*** 写入数据*/fun write():String{val path = AshmemoryTools.write(dataMap)dataMap.clear()return path}/*** 读出数据*/fun read(path:String,classLoader: ClassLoader){AshmemoryTools.read(path,classLoader)?.apply {val map: HashMap<String, List<Parcelable>?> = thisdataMap.putAll(map)}}fun <T:Parcelable> get(key:String,type: Type): List<T?> {val rawList = dataMap[key] ?: return emptyList()return convertParcelableListByType(rawList as MutableList<Parcelable?>, type)}fun delete(path:String){AshmemoryTools.delete(path)}fun <T : Parcelable?> convertParcelableListByType(parcelableList: MutableList<Parcelable?>,type: Type): MutableList<T?> {val result = mutableListOf<T?>()val targetClass = getRawType(type) as? Class<T> ?: return resultfor (item in parcelableList) {if (targetClass.isInstance(item)) {@Suppress("UNCHECKED_CAST")result.add(item as T)}}return result}private fun getRawType(type: Type): Class<*> {return when (type) {is Class<*> -> typeis ParameterizedType -> getRawType(type.rawType)else -> Any::class.java}}}
- 使用
//传递数据val path =AshmemCache().put("findVideoBeans", supFriendList1).write()val arouter = ARouter.getInstance().build(FRouter.POND_SNAP_VIDEO_MAIN).withString("ashmemPath", path).navigation() //获取数据AshmemCache ashmemCache = new AshmemCache(); ashmemCache.read(ashmemCachePath,this.getClass().getClassLoader());List<StickerVideoBean> videoBeans = ashmemCache.get("findVideoBeans",StickerVideoBean.class);//销毁ashmemCache.delete(ashmemCachePath);
六、总结
机制 | 是否突破 1MB-8k 限制 | 原理 |
---|---|---|
传统 Intent/Binder | 不能 | 数据被完整拷贝两次(进程A -> 内核 -> 进程B),受 Binder 缓冲区大小限制。 |
Ashmem(匿名共享内存 | 能 | 只传递一个小的文件描述符(fd),大数据存储在共享内存中,进程直接访问,无需大块拷贝。 |
unmarshall 的作用 | 不直接突破 | 它是 Parcelable 反序列化流程的一部分,负责在接收端从字节流(可能来自 Ashmem)重建对象。 |
因此,Android 系统正是通过匿名共享内存(Ashmem)来巧妙地规避了 Binder 的 1MB 传输限制。而 unmarshall 是这个过程中的一个关键环节,用于在接收方将传输过来的数据(可能存储在 Ashmem 中)重新组装成可用的对象。对于应用开发者来说,通常只需要使用系统提供的 Parcelable 对象(如 Bitmap)或 SharedMemory API 即可自动享受这一机制带来的好处。