Administrator
发布于 2026-08-25 / 15 阅读
0
0

系列文章《Android 工程师的 100 篇进阶笔记》第 7 篇:Android 跨进程通信:Binder、AIDL、Messenger 与 MMKV 选型

当你开始做大型 App,或者接入 SDK、插件化、多进程保活时,跨进程通信(IPC)是绕不开的坎。

Android 系统里几乎所有核心服务都在 SystemServer 进程里运行,你的 App 要和它们打交道,全靠 Binder。但很多工程师工作了几年,对 Binder 的理解还停留在“一个跨进程调用的东西”。

这篇文章我会把 IPC 讲清楚:

  1. Binder 为什么快。

  2. AIDL、Messenger、ContentProvider 分别适合什么场景。

  3. 多进程架构下数据共享怎么选(MMKV、SharedPreferences、Room)。


一、Binder:Android IPC 的底层答案

1.1 为什么不用 Socket / Pipe?

Linux 上有多种 IPC 机制:管道、消息队列、共享内存、Socket。但 Android 选了 Binder 作为核心 IPC,原因很现实:

机制

拷贝次数

安全性

调用方式

管道

2

弱

无结构化

Socket

2

弱

需要序列化

共享内存

0

弱

需要同步机制

Binder

1

强(UID/PID 校验)

面向对象调用

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?

场景

推荐方案

需要同步返回值,接口复杂

AIDL

单向通知,不需要复杂交互

Messenger

跨进程事件总线

ContentProvider + 观察者 / 自定义 IPC

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 高级工程师必须掌握的底层能力:

  1. Binder 只拷贝一次,安全性高,是 Android 选择它的核心原因。

  2. AIDL 适合复杂同步调用,Messenger 适合简单异步通知。

  3. 多进程数据共享优先 MMKV,结构化数据再用 ContentProvider。

  4. 多进程会让 Application 执行多次,单例失效,文件写操作要加锁。


下篇预告

下一篇《第 8 篇:Hilt + 依赖注入:从入门到架构层设计》会讲 Android 工程里的依赖注入。我们会覆盖:

  1. 为什么手动单例会逐渐失控。

  2. Hilt 的组件层级和生命周期绑定。

  3. 在模块化、多进程架构下怎么设计 DI 边界。


参考阅读

  • Android 官方文档:Bound Services / AIDL

  • 罗升阳《Android 系统源代码情景分析》Binder 章节

  • 微信 MMKV 官方文档

  • 上一篇文章:第 6 篇《Compose 性能——从 60FPS 到 120FPS 的真实路径》


评论