在 Android 里,我们每天都在用 Binder。

你调用 startActivity(),背后会走 Binder;
你调用 getSystemService(),背后会拿到 Binder 代理;
你访问 ActivityManagerServicePackageManagerService、WindowManager、ContentProvider,本质上也离不开 Binder。

很多 Android 开发者第一次听到 Binder,会把它理解成“进程间通信 IPC 的一种实现”。这当然没错,但还不够准确。

更准确地说:

Binder 是 Android 系统里最核心的 IPC 机制,它把跨进程调用伪装成一次普通的方法调用,并且同时解决了性能、身份识别、对象引用和权限边界问题。

这篇文章不做源码逐行解析,而是从工程视角讲清楚 Binder 到底解决了什么问题、一次 Binder 调用是怎么走的、AIDL 和 Binder 是什么关系,以及开发中应该注意哪些坑。


一、为什么 Android 需要 Binder?

Android 是一个多进程系统。

每个 App 默认运行在自己的进程里,系统服务大多运行在 system_server 进程里,底层 HAL、Native Service、Vendor Service 又可能运行在其他独立进程中。

这种设计有明显好处:

  1. 安全隔离:一个 App 不能随便读写另一个 App 的内存。

  2. 稳定性更好:某个应用崩溃,不应该拖垮整个系统。

  3. 权限边界清晰:系统服务可以集中管理敏感能力,比如相机、定位、包管理、窗口管理。

  4. 模块化:Framework、系统服务、App、HAL 可以分层演进。

但问题也来了:

进程之间内存隔离,A 进程不能直接调用 B 进程里的对象和方法。

比如你的 App 调用:

val activityManager = getSystemService(ActivityManager::class.java)

你拿到的并不是 system_server 进程里真正的 ActivityManagerService 对象,而是它在你当前进程里的一个“代理对象”。

这个代理对象会把你的方法调用打包,通过 Binder 驱动发送给真正的系统服务,再把结果返回给你。

所以 Binder 的核心价值就是:

在多进程隔离的前提下,让跨进程调用看起来像本地方法调用。


二、Binder 的前世:OpenBinder

Binder 并不是 Android 诞生后才凭空出现的技术。

它的历史可以追溯到更早的 OpenBinder。OpenBinder 最早来自 Be Inc.,后来随着相关团队和技术进入 Palm / PalmSource 体系,被用于 Palm OS Cobalt 等系统中。它的目标不是简单提供一个“进程间发消息”的工具,而是提供一套面向对象的系统级组件通信机制。

OpenBinder 里面已经能看到很多 Binder 的核心思想:

  • 进程之间可以暴露接口;

  • 调用方可以像调用对象方法一样调用远程接口;

  • 每个进程可以维护线程池来处理远程请求;

  • 系统负责管理跨进程对象引用;

  • 底层通过内核驱动和 ioctl 完成进程间通信。

这些思想和后来的 Android Binder 非常接近。

后来 Android 选择 Binder 作为系统核心 IPC 机制,并不是偶然。Android 需要把 App、Framework、系统服务、底层服务组织成一个高频通信的多进程系统。传统 Linux IPC,比如 Socket、Pipe、共享内存,当然也能通信,但它们更偏底层数据传输;而 Binder 更适合 Android 这种“调用系统服务就像调用本地对象”的编程模型。

不过这里要注意一个细节:

Android Binder 不是简单地把 OpenBinder 原封不动搬过来。

更准确地说,Android Binder 和 OpenBinder 有明显的历史传承关系,早期 Android 的设计也借鉴了 OpenBinder 的很多思想,但 Android 后来的 Binder 是面向 Android 系统重新定制和演进的实现。

所以如果一句话概括 Binder 的来历,可以这样说:

Binder 的思想源头可以追溯到 OpenBinder,Android 在此基础上发展出了适合自己系统架构的 Binder IPC 机制。

这也解释了为什么 Binder 看起来不像传统 Linux IPC。

它不是单纯为了“传字节流”而设计的,而是为了构建一个跨进程的对象调用体系:系统服务可以注册,客户端可以拿到代理对象,远程对象可以被引用,调用方身份可以被识别,权限边界可以被系统服务检查。

这正是 Android Framework 大量系统服务能够协同工作的基础。


二、Binder 里有哪些角色?

理解 Binder,先记住这几个角色。

1. Client:调用方

比如你的 App 进程。

它想调用系统服务,或者调用另一个进程里的 Service。

2. Server:服务方

比如 system_server 里的 ActivityManagerServicePackageManagerService,或者你自己写的远程 Service。

它真正实现业务逻辑。

3. Binder Driver:内核里的 Binder 驱动

Binder 驱动运行在 Linux 内核中,是通信的中转站。

它负责:

  • 接收 Client 发来的请求;

  • 找到目标 Server;

  • 管理 Binder 对象引用;

  • 唤醒 Server 端线程;

  • 把返回结果再送回 Client。

4. ServiceManager:服务注册中心

系统服务启动后,会把自己注册到 ServiceManager。

Client 想拿某个服务时,先去 ServiceManager 查询,拿到一个 Binder 引用,然后通过这个引用和目标服务通信。

你可以把 ServiceManager 理解成 Android 系统里的“电话簿”。

5. Proxy 和 Stub:代理和服务端骨架

在 AIDL 场景里,编译器会帮你生成两类代码:

  • Proxy:运行在 Client 进程,负责把方法调用打包成 Binder transaction。

  • Stub:运行在 Server 进程,负责接收 transaction,解析参数,然后调用真正的服务实现。

所以跨进程调用的大致感觉是:

Client 调用 Proxy
        ↓
Proxy 打包参数
        ↓
Binder Driver 转发
        ↓
Server 端 Stub 解包
        ↓
调用真正的业务方法

三、一次 Binder 调用到底怎么走?

假设 Client 想调用 Server 的方法:

val result = remoteService.add(1, 2)

如果这是一个跨进程 Binder 调用,它大致会经历下面几步。

第一步:Client 调用本地 Proxy 方法

Client 以为自己在调用一个普通对象的方法。

实际上这个对象是 Proxy。

Proxy 会做几件事:

  1. 创建一个 Parcel

  2. 把方法编号写进去;

  3. 把参数 12 写进去;

  4. 调用底层 transact() 发起 Binder 事务。

第二步:请求进入 Binder 驱动

Proxy 最终会通过系统调用进入 Binder Driver。

从用户态进入内核态后,Binder Driver 会根据目标 Binder 引用找到对应的 Server 进程和 Binder 对象。

第三步:Binder Driver 唤醒 Server 线程

Server 进程通常会有 Binder 线程池。

Binder Driver 会把请求投递给 Server 端的某个 Binder 线程。

如果线程池里有空闲线程,就唤醒它;如果线程不够,可能会创建或等待线程,具体取决于进程的 Binder 线程池配置。

第四步:Server 端 Stub 解析请求

Server 端 Stub 收到请求后,会从 Parcel 里读出方法编号和参数。

然后它调用真正的业务实现:

override fun add(a: Int, b: Int): Int {
    return a + b
}

第五步:结果返回给 Client

Server 把返回值写入 reply Parcel

Binder Driver 再把结果带回 Client 进程,Proxy 解析返回值,最终让 Client 得到:

3

从开发者视角看,这就是一次普通方法调用;
从系统视角看,这是一整套跨进程通信、线程调度、数据拷贝和权限边界处理。


四、Binder 为什么适合 Android?

Linux 本身已经有很多 IPC 机制,比如 Socket、Pipe、共享内存、信号等。

那 Android 为什么还要搞 Binder?

1. 它天然适合“远程方法调用”

Socket 更像是字节流通信。

你要自己定义协议、序列化、反序列化、请求编号、返回值匹配、连接管理。

Binder 则更像 RPC:

remoteService.doSomething()

调用方更像是在调用一个对象的方法。

这非常适合 Android Framework 这种大量系统服务接口的场景。

2. 它能传递“对象引用”

Binder 不只是传数据,还能传 Binder 对象引用。

这意味着 A 进程可以把一个回调接口传给 B 进程,B 进程之后还能反过来调用 A。

这就是很多系统回调机制的基础。

比如:

App 注册监听器给系统服务
系统服务在事件发生时回调 App

这背后通常也是 Binder 对象引用在发挥作用。

3. 它能识别调用方身份

Binder Driver 知道一次调用来自哪个进程、哪个 UID。

所以服务端可以根据调用方身份做权限校验。

例如系统服务可以判断:

val uid = Binder.getCallingUid()

然后检查这个 UID 是否拥有某个权限。

这对 Android 安全模型非常关键。

4. 它针对移动系统做了性能优化

Android 设备资源有限,IPC 又非常频繁。

Binder 在设计上尽量减少不必要的通信成本,并且通过 Binder 线程池、事务模型、对象引用管理等机制,让系统服务调用足够高效。

但要注意:

Binder 很快,不等于 Binder 没有成本。

一次 Binder 调用仍然涉及用户态和内核态切换、线程调度、数据打包和数据拷贝。

所以不要把 Binder 当成本地函数随便高频调用。


五、AIDL 和 Binder 是什么关系?

很多人会把 AIDL 和 Binder 混为一谈。

其实它们不是一回事。

Binder 是底层 IPC 机制

Binder 负责真正的跨进程通信。

它包括:

  • Binder Driver;

  • Binder 对象;

  • Binder transaction;

  • Binder 引用;

  • Binder 线程模型;

  • 权限身份传递。

AIDL 是接口描述语言

AIDL,全称 Android Interface Definition Language。

它的作用是:

用一个 .aidl 文件描述跨进程接口,然后由编译器生成 Proxy 和 Stub 代码。

例如定义一个接口:

interface ICalculator {
    int add(int a, int b);
}

编译后会生成对应的 Java/Kotlin 可调用接口代码。

你不用自己手写 transact(),也不用自己处理 Parcel 的读写细节。

简单说:

AIDL 是语法和代码生成工具
Binder 是真正的通信机制

不用 AIDL,也可以直接写 Binder;
但大多数业务场景下,用 AIDL 更清晰、更安全、更容易维护。


六、Binder 调用是同步还是异步?

默认情况下,Binder 调用是同步的。

也就是说:

remoteService.doSomething()

Client 发起调用后,会阻塞等待 Server 返回结果。

这和普通函数调用很像。

但 AIDL 也支持 oneway

oneway void notifyEvent(String event);

oneway 表示调用方发出请求后不等待返回结果。

不过不要误解:

oneway 不是“无限制异步队列”,也不是“开新线程执行”。

它只是让调用方不等待结果,服务端仍然需要 Binder 线程处理请求。如果请求太多,依然可能造成队列堆积。


七、Binder 线程池:为什么不要在 Binder 回调里做耗时操作?

Server 端处理 Binder 请求,靠的是 Binder 线程池。

如果你在 Binder 方法里做耗时操作,比如:

  • 网络请求;

  • 大量数据库查询;

  • 文件 IO;

  • 等待锁;

  • 长时间计算;

就会占住 Binder 线程。

当 Binder 线程都被占满后,新的跨进程请求就可能被阻塞,严重时会造成系统卡顿,甚至 ANR。

所以工程上一般建议:

override fun doHeavyWork() {
    workerExecutor.execute {
        // 真正的耗时任务放到业务线程池
    }
}

Binder 方法本身应该尽量短:

  1. 校验参数;

  2. 校验权限;

  3. 快速投递任务;

  4. 尽快返回。


八、Binder 的几个常见坑

1. 不要传大对象

Binder transaction buffer 有大小限制。

如果你通过 Intent、Bundle、AIDL 一次传太多数据,比如大 Bitmap、大数组、大 JSON,就可能遇到:

TransactionTooLargeException

常见场景包括:

  • Intent extra 里塞大对象;

  • onSaveInstanceState() 保存大量数据;

  • AIDL 方法一次返回巨大列表;

  • ContentProvider 查询结果过大。

正确做法是:

  • 大数据放文件、数据库、缓存;

  • 只通过 Binder 传 ID、URI、分页参数;

  • 大列表分页返回;

  • 图片等大对象走文件描述符或共享内存。

Binder 适合传控制信息和中小型结构化数据,不适合当大数据管道。


2. 不要忘记权限校验

跨进程接口就是边界。

只要你的 Service 暴露给其他进程,就不能假设调用方可信。

服务端应该校验:

  • 调用方 UID;

  • 调用方包名;

  • 调用方签名;

  • 调用方权限;

  • 参数是否合法。

例如:

val uid = Binder.getCallingUid()

但要注意,如果你调用了:

val token = Binder.clearCallingIdentity()

就会临时清除远程调用方身份,切换成当前进程自己的身份执行后续逻辑。

这在系统服务里很常见,但也容易误用。

用完后必须恢复:

Binder.restoreCallingIdentity(token)

否则很容易出现权限边界混乱。


3. 小心 Binder 死亡

远程进程可能崩溃。

Client 手里拿着的 Binder 代理对象,并不代表 Server 永远活着。

因此 Binder 提供了死亡通知机制:

binder.linkToDeath(deathRecipient, 0)

当远程 Binder 所在进程死亡时,Client 可以收到回调,然后重新绑定、清理资源或更新状态。

这在长连接式远程服务、系统服务代理、跨进程 SDK 中很重要。


4. 小心死锁

Binder 是同步调用时,Client 会等 Server 返回。

如果调用链变成这样:

A 调用 B,A 等待 B 返回
B 处理过程中又同步调用 A
A 的相关线程又在等待 B

就可能出现死锁。

尤其是跨进程回调、锁嵌套、主线程等待 Binder 返回时,要特别小心。

工程经验是:

  • 不要在持锁状态下发起远程 Binder 调用;

  • 不要在 Binder 回调里反向同步调用对方;

  • 不要在主线程等待复杂远程调用;

  • 远程回调尽量异步化。


九、Binder 在 Android 系统里的位置

Binder 几乎贯穿 Android Framework。

一些典型例子:

1. App 调系统服务

比如:

val pm = context.packageManager
val am = context.getSystemService(ActivityManager::class.java)
val wm = context.getSystemService(WindowManager::class.java)

这些 API 表面上是普通对象调用,底层很多都会进入系统服务,而系统服务大多在 system_server 进程中。

2. Activity 启动流程

你调用:

startActivity(intent)

并不是当前 App 自己直接创建目标 Activity。

真正的调度要经过系统服务。

这个过程中 App 和系统服务之间会发生多次 Binder 通信。

3. ContentProvider

访问 ContentProvider 本质上也是跨进程通信。

你调用:

contentResolver.query(uri, ...)

如果目标 Provider 在另一个进程里,就会通过 Binder 进行调用。

4. Bound Service

你写:

bindService(intent, connection, Context.BIND_AUTO_CREATE)

拿到的 IBinder 就是跨进程通信入口。

如果是本地 Service,可能直接返回本地 Binder;
如果是远程 Service,就会通过 Binder IPC 调用。

5. HAL 和系统底层服务

Android 系统和硬件抽象层之间,也大量依赖 Binder 体系。

早期有 HIDL,后来 Stable AIDL 逐步承担更多稳定接口定义的角色。


十、如何用一句话理解 Binder?

可以把 Binder 想象成:

Android 系统里的“内线电话系统”。

  • ServiceManager 是电话簿;

  • Client 是打电话的人;

  • Server 是接电话的人;

  • Proxy 是你手里的电话听筒;

  • Stub 是服务端接线员;

  • Binder Driver 是电话交换机;

  • Parcel 是通话内容的打包格式;

  • UID/PID 是来电显示;

  • 权限校验是门禁系统。

这套系统让 Android 在多进程隔离的情况下,依然能高效地组织 App、Framework、系统服务和底层服务之间的调用关系。


十一、面试时怎么回答 Binder?

如果面试官问:“讲讲 Android Binder。”

可以按照这个结构回答:

第一层:定义

Binder 是 Android 的核心 IPC 机制,用于不同进程之间通信。它可以让一个进程像调用本地方法一样调用另一个进程中的服务方法。

第二层:角色

Binder 主要涉及 Client、Server、ServiceManager、Binder Driver、Proxy、Stub、Parcel。

Client 通过 Proxy 发起调用,Binder Driver 在内核中负责转发,Server 端 Stub 解析请求并调用真正的服务实现。

第三层:调用流程

Client 调用 Proxy 方法后,Proxy 把方法编号和参数写入 Parcel,通过 transact() 进入 Binder Driver。Binder Driver 找到目标 Server,唤醒 Binder 线程,Server 端 Stub 解包并执行方法,再把结果返回给 Client。

第四层:优势

Binder 适合 Android 的原因主要有:

  1. 支持面向对象式的远程方法调用;

  2. 能传递 Binder 对象引用;

  3. 能识别调用方 UID/PID,方便做权限校验;

  4. 针对移动设备上的高频 IPC 做了优化;

  5. 和 Android Framework 的系统服务模型高度匹配。

第五层:注意点

Binder 虽然高效,但不是零成本。开发时要注意:

  • 不要传大对象;

  • 不要在 Binder 线程里做耗时任务;

  • 注意权限校验;

  • 注意远程进程死亡;

  • 避免同步调用造成死锁;

  • oneway 不等于无限异步能力。

这样回答,基本就从概念、架构、流程、优势、工程经验几个角度覆盖完整了。


总结

Binder 不是 Android 里的一个普通通信工具,而是整个 Android 系统架构的基础设施。

它把 App、Framework、系统服务、底层服务连接在一起,让 Android 能在多进程、安全隔离的前提下,仍然保持统一的编程模型。

理解 Binder 后,很多 Android 机制都会变得更清晰:

  • 为什么 startActivity() 不是简单 new 一个 Activity;

  • 为什么 getSystemService() 拿到的是代理;

  • 为什么 Intent 和 Bundle 不能乱塞大对象;

  • 为什么系统服务能知道是谁在调用它;

  • 为什么跨进程接口一定要做权限校验;

  • 为什么 Binder 线程阻塞可能导致 ANR。

一句话总结:

Binder 是 Android 的通信骨架。理解 Binder,才算真正开始理解 Android Framework。