当你开始做大型 App,或者接入 SDK、插件化、多进程保活时,跨进程通信(IPC)是绕不开的坎。
Android 系统里几乎所有核心服务都在 SystemServer 进程里运行,你的 App 要和它们打交道,全靠 Binder。但很多工程师工作了几年,对 Binder 的理解还停留在“一个跨进程调用的东西”。
这篇文章我会把 IPC 讲清楚:
Binder 为什么快。
AIDL、Messenger、ContentProvider 分别适合什么场景。
多进程架构下数据共享怎么选(MMKV、SharedPreferences、Room)。
一、Binder:Android IPC 的底层答案
1.1 为什么不用 Socket / Pipe?
Linux 上有多种 IPC 机制:管道、消息队列、共享内存、Socket。但 Android 选了 Binder 作为核心 IPC,原因很现实:
Binder 只拷贝一次数据,而且调用端可以直接拿到远端对象的代理(Proxy),用起来像本地方法调用。
1.2 Binder 通信的基本流程
App 进程 A
Client
│
▼
BinderProxy ───────► Binder Driver(内核) ───────► Binder Stub
│ │
│◄────────────── 返回结果 ◄────────────────────│
│ │
▼ ▼
调用本地方法 Server 进程 B
关键点:
Client拿到的是Proxy,调用被转发到Binder Driver。Binder Driver找到目标进程,把数据写入 Server 的内存映射区。Server 端的
Stub收到调用,执行真正的业务逻辑。
1.3 Binder 驱动的关键优化
Binder 用到了 mmap(内存映射)。Client 把数据写入自己的内存,Binder Driver 直接把这块内存映射到 Server 进程空间,只发生一次拷贝。
而传统 Socket/Pipe 需要:
Client 用户态 → 内核态(拷贝 1)
内核态 → Server 用户态(拷贝 2)
这是 Binder 性能优势的根源。
二、AIDL:结构化跨进程调用
AIDL(Android Interface Definition Language)是 Binder 的上层封装,让你像写接口一样定义跨进程服务。
2.1 最小可运行示例
定义接口 IBookManager.aidl:
// IBookManager.aidl
package com.example.ipc;
import com.example.ipc.Book;
interface IBookManager {
List<Book> getBookList();
void addBook(in Book book);
}
对应的 Book 也要实现 Parcelable:
parcelize
class Book(
val id: Int,
val name: String
) : Parcelable
服务端实现:
class BookManagerService : Service() {
private val books = mutableListOf<Book>()
private val binder = object : IBookManager.Stub() {
override fun getBookList(): List<Book> = books
override fun addBook(book: Book?) {
book?.let { books.add(it) }
}
}
override fun onBind(intent: Intent?): IBinder = binder
}
客户端绑定:
val connection = object : ServiceConnection {
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
val bookManager = IBookManager.Stub.asInterface(service)
bookManager.addBook(Book(1, "Android 进阶"))
val list = bookManager.getBookList()
}
override fun onServiceDisconnected(name: ComponentName?) {}
}
bindService(
Intent(this, BookManagerService::class.java),
connection,
Context.BIND_AUTO_CREATE
)
2.2 AIDL 的坑:跨进程对象不是同一个
很多人在 AIDL 里传一个回调接口,希望服务端回调时拿到同一个对象。但跨进程时,所有对象都会被序列化/反序列化,内存地址不同。
如果需要在服务端保持同一个回调,用 RemoteCallbackList:
private val listeners = RemoteCallbackList<IOnNewBookArrivedListener>()
listeners.register(listener)
listeners.beginBroadcast()
for (i in 0 until listeners.size) {
listeners.getBroadcastItem(i).onNewBook(book)
}
listeners.finishBroadcast()
三、Messenger:轻量级异步 IPC
Messenger 是对 AIDL 的进一步封装,底层还是 Binder,但它把调用变成 Handler + Message 模型。
3.1 什么时候用 Messenger?
3.2 Messenger 服务端示例
class MessageService : Service() {
private val handler = Handler(Looper.getMainLooper()) { msg ->
val replyTo = msg.replyTo
replyTo?.send(Message.obtain(null, 1, "收到: ${msg.what}"))
true
}
private val messenger = Messenger(handler)
override fun onBind(intent: Intent?): IBinder = messenger.binder
}
客户端:
val connection = object : ServiceConnection {
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
val messenger = Messenger(service)
val msg = Message.obtain(null, 100)
msg.replyTo = localMessenger // 用于接收回复
messenger.send(msg)
}
}
Messenger 的好处是简单,坏处是无法做同步调用,也不适合高并发。
四、多进程架构下的数据共享选型
4.1 SharedPreferences 真的不能跨进程
很多人以为 MODE_MULTI_PROCESS 能救 SharedPreferences,但实际上它只是每次读写前重新加载文件,性能差且不可靠。官方已废弃这个模式。
4.2 MMKV:跨进程共享的最优解之一
MMKV 是微信开源的键值存储,底层用 mmap,支持多进程访问。
// 初始化
MMKV.initialize(this)
// 多进程模式
val mmkv = MMKV.mmkvWithID("inter_process", MMKV.MULTI_PROCESS_MODE)
mmkv.encode("user_id", "12345")
val userId = mmkv.decodeString("user_id")
优点:
读写性能远高于 SharedPreferences。
支持多进程共享。
支持加密。
缺点:
不适合做复杂查询。
key 和 value 有大小限制。
4.3 ContentProvider:结构化数据共享
如果你要共享的是结构化数据(如联系人、媒体库),ContentProvider 仍然是官方推荐方案。
class NoteProvider : ContentProvider() {
override fun query(...): Cursor? {
// 查询 SQLite
}
}
但 ContentProvider 的缺点是:
每次调用都有跨进程开销。
线程模型容易阻塞主线程。
权限管理复杂。
4.4 Room + ContentProvider?
不推荐。Room 本身是进程内 ORM,跨进程共享需要再包一层 ContentProvider,复杂度高。除非必须共享数据库,否则优先用 MMKV 或文件共享。
五、多进程架构的常见坑
5.1 Application 会执行多次
每个进程都会启动一个 Application 实例。如果你在 Application.onCreate 里初始化全局配置,要判断进程名:
val processName = Application.getProcessName()
if (processName == packageName) {
// 主进程初始化
}
否则所有 SDK 会在子进程里也初始化一遍,浪费内存。
5.2 单例模式跨进程失效
进程 A 里的单例和进程 B 里的单例不是同一个对象。不要指望靠单例做跨进程状态同步。
5.3 文件锁竞争
多进程同时写同一个文件必须用文件锁(FileLock),否则数据会损坏。
六、选型决策树
需要跨进程通信?
├── 是,且需要同步返回复杂数据 → AIDL
├── 是,且只是单向/异步通知 → Messenger
├── 需要共享键值对数据 → MMKV(多进程模式)
├── 需要共享结构化数据 → ContentProvider + SQLite
└── 不需要,只在单进程内 → 普通 Service / Room / SharedPreferences
小结
IPC 是 Android 高级工程师必须掌握的底层能力:
Binder 只拷贝一次,安全性高,是 Android 选择它的核心原因。
AIDL 适合复杂同步调用,Messenger 适合简单异步通知。
多进程数据共享优先 MMKV,结构化数据再用 ContentProvider。
多进程会让 Application 执行多次,单例失效,文件写操作要加锁。
下篇预告
下一篇《第 8 篇:Hilt + 依赖注入:从入门到架构层设计》会讲 Android 工程里的依赖注入。我们会覆盖:
为什么手动单例会逐渐失控。
Hilt 的组件层级和生命周期绑定。
在模块化、多进程架构下怎么设计 DI 边界。
参考阅读
Android 官方文档:Bound Services / AIDL
罗升阳《Android 系统源代码情景分析》Binder 章节
微信 MMKV 官方文档
上一篇文章:第 6 篇《Compose 性能——从 60FPS 到 120FPS 的真实路径》