- 教程
- 技术博客
- 文档
【免费下载链接】YCBlogs
技术博客笔记大汇总,包括Java基础,线程,并发,数据结构;Android技术博客等等;常用设计模式;常见的算法;网络协议知识点;部分flutter笔记;还包括平时开发中遇到的bug汇总,当然也在工作之余收集了大量的面试题,长期更新维护并且修正,持续完善……开源的文件是markdown格式的!转载请注明出处,谢谢!
导读:本文以 Android 系统最核心的跨进程通信机制 Binder 为主线,从"什么是 Binder"的四个维度切入,系统剖析 Android 为何舍弃 Linux 传统 IPC 而自研 Binder 的性能与安全考量,深入讲解 Binder 驱动管理的线程池模型、ServiceManager 注册/查询机制、四大角色协作流程,并结合应用进程与 SystemServer 进程间的 AMS 通信实例与仓库中 AIDL 进程间通信详细介绍 的源码级实现,帮助读者真正理解
bindService背后一次跨进程调用的完整旅程。
01 什么是 Binder:从四个维度理解 Binder 的身份
Binder 一词在 Android 生态中几乎无处不在,但它在不同层面有截然不同的含义。理解 Binder,需要从以下四个维度分别审视:
1. 从类定义的角度:直观来说,Binder 是 Android 中的一个类,它继承了IBinder接口。IBinder定义了跨进程对象的基本操作契约,而Binder类则是它的具体实现。
2. 从 IPC(进程间通信)的角度:Binder 是 Android 中一种独特的跨进程通信方式。它可以被理解为一种虚拟的物理设备,其设备驱动为/dev/binder。这种通信方式在 Linux 中并不存在,是 Android 特有的内核级通信通道。
3. 从 Android Framework 的角度:Binder 是ServiceManager连接各种 Manager(如ActivityManager、WindowManager等)和相应 ManagerService 的桥梁。系统服务(如 AMS、WMS)正是通过这条桥梁向应用层暴露能力的。
4. 从 Android 应用层的角度:Binder 是客户端和服务端进行通信的媒介。当你执行bindService时,服务端会返回一个包含了服务端业务调用的 Binder 对象,通过这个 Binder 对象,客户端就可以获取服务端提供的服务或数据。这里的服务包括普通服务和基于 AIDL 的服务——后者正是仓库中 AIDL 基础介绍 所描述的面向对象 IPC 方案。
从源码结构看,AIDL 接口编译后生成的Stub类本质上就是一个 Binder 类,它内部持有客户端代理Proxy,两者共同构成了 Binder 通信的应用层载体(详见 AIDL 进程间通信详细介绍 第 5 节的源码解析部分)。
02 为什么要使用 Binder:性能与安全的双重考量
在传统的 Linux 系统上,实现进程间通信的手段并不匮乏:管道(Pipe)、System V 消息队列、共享内存、Socket 等早已成熟。那么 Android 为什么没有直接沿用这些方案,而是投入成本开发一套全新的 Binder 机制?
最简洁的回答只有两点:性能——相比传统 Socket 更高效;安全——安全性高,支持通信双方进行身份验证。展开来看,主要源于以下考量。
2.1 传统 IPC 方案的效率缺陷
跨进程通信中,只有 Socket 天然支持 Client-Server 通信模式,但 Socket 作为一款通用接口,其传输效率低、开销大,主要适合跨网络的进程间通信和本机进程间的低速通信场景。
消息队列和管道采用存储-转发方式:数据先从发送方缓冲区拷贝到内核开辟的缓冲区,然后再从内核缓冲区拷贝到接收方缓冲区,至少经历两次拷贝。在移动设备这种性能受限、需要省电的场景下,广泛使用跨进程通信对通信机制的性能要求极为苛刻,两次拷贝的代价难以接受。
共享内存虽然一次内存拷贝都不需要,但它的控制逻辑复杂,难以使用,同样不适合作为系统级的通用 IPC 方案。
2.2 Binder 的性能优势:只需一次拷贝
Binder 的传输过程只需要一次内存拷贝,这使其在性能上显著优于管道、消息队列和 Socket。数据拷贝次数的对比如下:
| 通信方式 | 数据拷贝次数 | 备注 |
|---|---|---|
| Binder | 1 次 | Android 自研,兼顾性能与易用性 |
| 管道 / 消息队列 | 2 次 | 存储-转发模式 |
| Socket | 2 次 | 通用接口,开销大 |
| 共享内存 | 0 次 | 无需拷贝但控制复杂 |
2.3 Binder 的安全优势:内核级身份验证
传统 IPC 在安全性上有两个致命短板:
- 无法可靠获取对方身份:传统 IPC 的接收方无法获得对方进程可靠的 UID 和 PID(用户 ID 和进程 ID),从而无法鉴别对方身份。Android 为每个安装好的应用程序分配了独立的 UID,因此 UID 是鉴别进程身份的重要标志。使用传统 IPC 时,UID/PID 只能由用户在数据包中手工填入,很容易被伪造,被恶意程序利用。可靠的身份标记必须由 IPC 机制本身在内核中添加——这正是 Binder 驱动所擅长的。
- 接入点完全开放:传统 IPC 的访问接入点是开放的,无法建立私有通道。例如命名管道的名称、System V 的键值、Socket 的 IP 地址或文件名都是公开的,任何知道这些接入点的程序都可以与对端建立连接,无法阻止恶意程序通过猜测接收方地址获得连接。
基于以上原因,Android 需要建立一套全新的 IPC 机制来满足系统对通信方式、传输性能和安全性的综合要求,这就是 Binder。Binder 基于 Client-Server 通信模式,传输过程只需一次拷贝,为发送方自动添加 UID/PID 身份标记,既支持实名 Binder 也支持匿名 Binder,安全性高。
2.4 额外的收益:面向对象的调用方式
Binder 还带来一个体验上的好处:它天然支持面向对象的调用方式。使用 Binder 时,跨进程调用一个远端对象的方法,就如同调用本地实例一样自然。这一点在应用层体现得尤为明显——客户端拿到IBinder后通过Stub.asInterface()转换,就能像调用本地接口一样调用服务端方法,底层复杂的跨进程细节全部由框架与驱动屏蔽。
03 Binder 如何进行线程管理:驱动统一调度
一个需要开发者深入了解的细节是:Binder 服务端进程的线程是如何创建与管理的?
每个 Binder 的 Server 进程会创建很多线程来处理 Binder 请求,可以简单理解为创建了一个Binder 线程池(虽然实际上的管理方式并不完全等价于普通线程池)。关键在于:真正管理这些线程的并不是 Server 端进程,而是 Binder 驱动。
驱动层面对线程数量有着硬性约束:
- 一个进程的 Binder 线程数默认最大是 16,超过这个数量的请求会被阻塞,等待空闲的 Binder 线程出现后再继续执行。
理解这一点,对做进程间通信时的并发问题就能做到心中有数。例如使用 ContentProvider(又一个基于 Binder 机制的组件,详见 ContentProvider 分析)时,就能明确知道它的 CRUD(创建、检索、更新和删除)方法最多只能同时有 16 个线程在跑,超出部分必须排队等待。
这个约束在 AIDL 场景下同样成立:AIDL 服务端Stub中实现的方法运行在 Binder 线程池中(详见 AIDL 进程间通信详细介绍 的源码注释),因此服务端实现应采用同步方式编写,因为它本身已经运行在独立线程中,不需要额外开线程。
小结:Binder 到底讲的是什么
通常意义上,Binder 指的就是 Android 的通信机制,但从不同主体来看:
- 对于服务端进程来说,Binder 指的是Binder 本地对象;
- 对于客户端进程来说,Binder 指的是Binder 代理对象;
- 对于传输过程来说,Binder 是可以跨进程传递的对象。
04 Binder 的工作流程:一次跨进程调用的完整旅程
Binder 的工作流程可以拆解为以下五个步骤:
- 获取代理对象:客户端首先获取服务器端的代理对象。所谓的代理对象,实际上是在客户端建立一个服务端的"引用",该代理对象具备服务端的功能,使客户端访问服务端方法就像访问本地方法一样。
- 发送请求:客户端通过调用服务器代理对象的方式向服务器端发送请求。
- 驱动转发:代理对象将用户请求通过 Binder 驱动发送到服务器进程。
- 服务端处理并返回:服务器进程处理用户请求,并通过 Binder 驱动将处理结果返回给客户端的服务器代理对象。
- 客户端接收结果:客户端收到服务端的返回结果。
值得注意的是,应用层这一"像调用本地方法"的体验,正是由 AIDL 生成的Stub与其内部Proxy类协作实现的:当服务端和客户端位于同一进程时,方法调用不会走跨进程的transact过程;当两者处于不同进程时,方法调用才走transact过程,这个逻辑由Stub的内部代理类Proxy完成(见 AIDL 进程间通信详细介绍 5.1 节对生成 Java 文件的分析)。
Binder 主要能提供哪些功能
- 用驱动程序来推进进程间的通信;
- 通过共享内存来提高性能;
- 为进程请求分配每个进程的线程池;
- 针对系统中的对象引入引用计数和跨进程的对象引用映射;
- 支持进程间同步调用。
05 Binder 通信机制原理:ServiceManager 与代理对象的秘密
Binder 通信机制的核心原理可以概括为"注册 — 查询 — 代理转发"三段式,其运转依赖ServiceManager这个系统级中枢。
第一步:Server 进程向 ServiceManager 注册。Server 进程告诉 ServiceManager:我是谁、我有什么、我能做什么。此时一张"名字 → Binder 实体"的映射关系表便生成了。
第二步:Client 进程向 ServiceManager 查询。Client 进程发起查询:我要调用 Server 进程某个对象的方法。这个查询过程要经过 Binder 驱动,此时 Binder 驱动开始发挥它的关键作用。
第三步:驱动返回代理对象而非实体。当向 ServiceManager 查询完毕,Binder 驱动是否直接把 Server 的真实对象返回给 Client 进程?其实不然。Binder 驱动将 Server 的真实对象转换成了Proxy 代理对象并转发给 Client 进程。因此 Client 进程拿到的并不是真实对象,而是一个代理对象。
第四步:代理对象的方法调用。代理对象拥有与原对象同名的方法(否则就无法"欺骗"Client 了),但这个同名方法只是对参数进行一些包装。当 Client 进程调用这个方法时,消息被发送给 Binder 驱动,驱动发现调用方持有的是代理对象,便通知 Server 进程:"调用你那个真实对象的对应方法,把结果给我。"Server 进程将计算结果发送给驱动,驱动再转发给 Client 进程。
此时 Client 进程还蒙在鼓里——它以为自己调用的是真实对象的方法,其实只是调用了代理对象。不过 Client 最终拿到了计算结果,目的达成。
用 AIDL 案例印证代理机制
仓库中 AIDL 进程间通信详细介绍 从源码层面印证了这套代理机制:
- 客户端在
onServiceConnected中通过ICheckAppInfoManager.Stub.asInterface(service)将服务端返回的IBinder对象转换成 AIDL 接口对象; asInterface()是区分进程的:同一进程时返回Stub本身,不同进程时返回Stub.proxy代理对象;- AIDL 生成的 Java 文件为每个接口方法声明了独立的
id标识,用于在transact过程中标识客户端请求的到底是哪个方法。
这套机制与上文的 computer/computerProxy 类比完全同构:Stub 对应 Binder 本地对象,Proxy 对应 Binder 代理对象,Binder 驱动负责两者间的中转。
06 Binder 运行机制:四大角色与互联网类比
Binder 基于 Client-Server 通信模式,除 Client 端和 Server 端外,还有两个角色共同合作完成进程间通信功能。Binder 通信的四个角色如下:
| 角色 | 职责说明 |
|---|---|
| Client 进程 | 使用服务的进程 |
| Server 进程 | 提供服务的进程 |
| ServiceManager 进程 | 将字符形式的 Binder 名字转化为 Client 中对 Binder 的引用,使 Client 能够通过 Binder 名字获得对 Server 中 Binder 实体的引用 |
| Binder 驱动 | 负责进程之间 Binder 通信的建立、Binder 在进程之间的传递、Binder 引用计数管理、数据包在进程之间的传递和交互等一系列底层支持 |
初次接触这些概念会觉得难以理解,可以把四个角色和熟悉的互联网进行类比:
- Server是服务器;
- Client是客户终端;
- ServiceManager是域名服务器(DNS);
- Binder 驱动是路由器。
类比之下,整个体系就清晰了:Client 像访问网站一样"按名字"找到服务,DNS(ServiceManager)负责把名字解析成地址,路由器(Binder 驱动)负责数据包在两端之间的可靠送达。
07 实战案例:AMS 的 Binder 通信剖析
我们知道应用进程与 SystemServer 进程属于两个不同的进程,进程之间需要通信。以系统中最典型的AMS(ActivityManagerService)通信为例,可以完整看到 Binder 在真实系统服务中的落位。
AMS 通信的类结构:Android 系统采用了自身设计的 Binder 机制,其中ActivityManagerProxy和ActivityManagerNative都继承自IActivityManager,而 SystemServer 进程中的ActivityManagerService对象则继承自ActivityManagerNative。
简单的层级表示为:
Binder 接口 (IActivityManager) ├── ActivityManagerNative(Binder 本地对象基类,实现 Binder 通信骨架) │ └── ActivityManagerService(SystemServer 中的真实服务实体) └── ActivityManagerProxy(客户端持有的 Binder 代理对象)这样划分角色后:
ActivityManagerNative与ActivityManagerProxy相当于一个Binder 的客户端;ActivityManagerService相当于Binder 的服务端;- 当
ActivityManagerNative调用接口方法的时候,底层通过 Binder 驱动将请求数据与请求传递给 Server 端,并在 Server 端执行具体的接口逻辑。
这与 第 05 节 阐述的"本地对象 + 代理对象"模型完全一致,AMS 就是这套模型在系统级服务上的真实投影。
值得注意:Binder 机制的单向性
需要注意的是,Binder 机制是单向的、异步的:只能通过 Client 端向 Server 端传递数据与请求,且调用方无需等待服务端的返回(也无法直接返回)。那么问题来了:如果 SystemServer 进程想向应用进程传递数据怎么办?
答案是需要重新定义一个 Binder 请求——以 SystemServer 为 Client 端、以应用进程为 Server 端,从而在两个进程之间实现双向通信。这一点在 AIDL 开发中同样常见:客户端通过Message.replyTo携带自己的Messenger给服务端,就是为"服务端主动回调客户端"搭建反向通道(详见 IPC 通信方式介绍 中 Messenger 的完整代码示例)。
08 延伸:基于 Binder 的 IPC 家族与工程实践要点
理解了 Binder 机制本身之后,再回头看 Android 提供的各类 IPC 方案就会豁然开朗——它们大多只是 Binder 在不同抽象层次上的封装:
| IPC 方案 | 与 Binder 的关系 |
|---|---|
| Messenger | 轻量级 IPC 方案,底层实现是 AIDL(即 Binder),以串行方式处理消息,无需考虑线程同步 |
| AIDL | 直接暴露 Binder 接口,支持并发和跨进程调用方法,是 Messenger 的"完整版" |
| ContentProvider | 底层采用 Binder 机制,用于跨应用数据共享 |
| Intent / 文件 / Socket | 非 Binder 路线,各有适用场景 |
其中 AIDL 是与 Binder 机制结合最紧密、最能体现其原理的开发形态,几个工程要点值得掌握(完整案例见 AIDL 进程间通信详细介绍):
1. 服务端与客户端的分工:服务端在Service.onBind()中返回Stub实例(即 Binder 本地对象);客户端绑定服务后通过Stub.asInterface(service)拿到代理对象(同一进程返回 Stub 本身,跨进程返回 Proxy)。
2. 处理 Binder 意外死亡:Binder 是会意外死亡的。如果服务端进程异常终止,会导致远程调用失败。Android 提供了linkToDeath和unlinkToDeath两个配对方法:通过linkToDeath给 Binder 设置死亡代理,当 Binder 死亡时收到binderDied()通知,在回调中清理引用并重新绑定服务。
3. 服务端权限校验:可以在清单文件中声明自定义权限,并在onBind()中通过checkCallingOrSelfPermission()校验调用方权限,不满足则返回 null 拒绝服务,进一步提升安全性。
4. 耗时操作与 ANR 风险:客户端调用远程方法时会挂起等待服务端返回,远程调用是耗时的,因此不应在 UI 线程发起耗时远程调用;而服务端Stub方法运行在 Binder 线程池中,应采用同步方式实现。
结语
Binder 作为 Android 系统的"通信底座",支撑着 AMS、WMS、ContentProvider、AIDL、Messenger 等几乎所有跨进程场景。理解它的设计动机(一次拷贝的性能、内核级 UID/PID 的安全)、线程模型(驱动管理的 16 线程池)、工作流程(代理对象 + 驱动转发)与四大角色(Client、Server、ServiceManager、驱动),就能在开发中准确预判 IPC 的并发上限、阻塞风险与安全边界。本文的配套资料还包括 IPC 通信方式介绍(六种 IPC 方案对比与完整代码)、IPC 之序列化(Parcelable 序列化细节)、IPC 之线程进程(进程与线程模型)等,可继续深入阅读。
- 教程
- 技术博客
- 文档
【免费下载链接】YCBlogs
技术博客笔记大汇总,包括Java基础,线程,并发,数据结构;Android技术博客等等;常用设计模式;常见的算法;网络协议知识点;部分flutter笔记;还包括平时开发中遇到的bug汇总,当然也在工作之余收集了大量的面试题,长期更新维护并且修正,持续完善……开源的文件是markdown格式的!转载请注明出处,谢谢!
相关推荐
Android开源项目分析深度探索:揭秘Binder进程通信机制的实现原理
Android开源项目分析深度探索:揭秘Binder进程通信机制的实现原理 Binder是Android系统中最重要的进程间通信机制,它为Android四大组件
文档教程技术博客RQAlpha事件驱动机制深度解析:从核心原理到实战应用
RQAlpha事件驱动机制深度解析:从核心原理到实战应用 RQAlpha作为一款基于Python的开源量化交易框架,其强大的事件驱动机制是实现高效回测和交易的核
金融科技为什么选择Indigo?探索Scala函数式游戏开发的终极优势
为什么选择Indigo?探索Scala函数式游戏开发的终极优势 Indigo是一个基于Scala的函数式游戏引擎,它将函数式编程的优雅与游戏开发的创造力完美结合
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考