使用 Bundle 进行 IPC

Android 进程间通信的数据容器详解

Parcelable 数据传递 Intent/Messenger ContentProvider

目录导航

一、为什么用 Bundle 做 IPC?

1 核心优势
  • 数据容器:Bundle 是 Android 官方提供的轻量级、类型安全的 Parcelable容器
  • 天然支持跨进程:所有需要跨进程携带数据的地方,底层最终都是用 Bundle 打包传递
  • 序列化自动完成:将对象放入 Bundle,调用 put 方法时自动触发序列化;取出时自动反序列化,无需手动编码
重要纠正:Bundle 是 Parcel 的一个高级封装,内部把键值对逐个写入 Parcel。Bundle 不是唯一的数据载体,而是 Android 在部分 IPC 通道中强制要求或推荐使用的数据打包方式。

二、Bundle 支持的数据类型(跨进程)

1 支持类型清单
类别 具体类型 备注
基本类型 int, long, double, float, boolean, byte, char, short -
基本类型数组 int[], long[], byte[] -
字符串 String, CharSequence, String[] -
Parcelable 对象 Parcelable及其子类 推荐:高效
Parcelable 列表 ArrayList<? extends Parcelable> 必须用专用方法
Parcelable 数组 Array<? extends Parcelable> 必须用专用方法
Serializable 对象 Serializable 不推荐,性能差
Bundle 嵌套 Bundle -
特殊类型 Size, SizeF, ParcelFileDescriptor -
严格注意:不能直接 put List<Parcelable>Map等,必须使用对应的专用方法。

三、跨进程传递 Bundle 的三大通道

3.1 通过 Intent 传递

发送端:

val bundle = Bundle().apply {
    putString("name", "张三")
    putInt("age", 28)
    putParcelableArrayList("books", booksArrayList)
}
intent.putExtra("data_bundle", bundle)

接收端:

val bundle = intent.getBundleExtra("data_bundle")
val name = bundle.getString("name")
val age = bundle.getInt("age", 0)
3.2 通过 Messenger 配合 Bundle

服务端:

override fun handleMessage(msg: Message) {
    val bundle = msg.data
    val replyBundle = Bundle().apply { putString("reply", "ok") }
    msg.replyTo?.send(Message.obtain().apply { data = replyBundle })
}

客户端:发送 Message 时通过 msg.data设置 Bundle,并设置 msg.replyTo接收回复。

3.3 通过 ContentProvider.call() 同步调用

服务端:

override fun call(method: String, arg: String?, extras: Bundle?): Bundle? {
    return Bundle().apply { putParcelableArrayList("list", resultList) }
}

客户端:

val result = contentResolver.call(uri, "METHOD", null, requestBundle)
val list = result?.getParcelableArrayList<Book>("list", Book::class.java)

四、关键问题:集合类型的正确传递

1 正确方法(根据变量类型选用)
你的变量类型 进 Bundle 的方法 取出的方法
ArrayList<Book> putParcelableArrayList(key, list) getParcelableArrayList(key, Book::class.java)
Array<Book> putParcelableArray(key, array) getParcelableArray(key, Book::class.java)
List<Book>(编译时) 先转 ArrayList(list)再用上面方法 同 ArrayList
2 常见错误与解决
错误 1

putParcelable(key, list)→ 类型不匹配,因为 putParcelable期望单个对象

错误 2

putParcelableArray(key, arrayList)→ 类型不匹配,因为期望数组

错误 3

将可空 List<Book>?直接传给期望 Collection<out Book>的 Java 方法 → 需要 list?.let { ArrayList(it) }处理可空并转为可变集合

3 为什么必须用专用方法?

Bundle 内部通过 writeToParcel序列化对象,集合的序列化逻辑和单对象不同。putParcelableArrayListputParcelableArray专门处理集合的写入和类型标记,保证反序列化回到正确类型。

五、Parcelable 数据类的定义(跨进程共享)

1 Kotlin @Parcelize 实现

使用 Kotlin @Parcelize最便捷(需在公共库中):

@Parcelize
data class Book(val title: String, val author: String) : Parcelable
  • 两边的包名、类名、字段顺序必须完全一致
  • 需要 AIDL 传递时,还要创建对应的 Book.aidl文件声明

六、API 弃用与兼容(Android 13+)

1 新旧方法对比

从 API 33 开始,不带 Class<T>参数的获取方法被弃用,推荐显式传入类信息。

旧方法(弃用) 新方法(API 33+)
bundle.getParcelableArrayList<Book>("key") bundle.getParcelableArrayList("key", Book::class.java)
bundle.getParcelable<Book>("key") bundle.getParcelable("key", Book::class.java)
bundle.getParcelableArray("key") bundle.getParcelableArray("key", Book::class.java)
2 兼容低版本
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
    bundle.getParcelableArrayList("key", Book::class.java)
} else {
    @Suppress("DEPRECATION")
    bundle.getParcelableArrayList("key")
}

七、注意事项与陷阱

1 核心注意事项
  • 数据大小限制:整个 Bundle 跨进程传输受 Binder 限制,约 1MB,实际建议 < 500KB。超出会抛 TransactionTooLargeException。大文件应传递 URI 或文件路径
  • 类定义一致:两端使用的 Parcelable 类必须完全相同(包名+字段),建议放入 shared 模块
  • 线程安全:Bundle 线程不安全,创建和修改应在同一线程完成,发送后不要再修改
  • 性能:优先使用 Parcelable,避免 Serializable
  • 可空集合:从 AIDL 或其他接口得到的 List?要处理空值,且转为 ArrayList 才能放入 Bundle
  • ClassLoader:自定义 Parcelable 在反序列化时可能遇到类加载器问题,系统 API(如 Intent、Bundle 已内部处理),特殊场景记得设置 bundle.setClassLoader()

八、总结

Bundle IPC 核心要点

至此,你已掌握 Android 中最基础、最通用的 IPC 方式。