1. Binder 通信模型与 Android 进程隔离

Android 基于 Linux 内核,通过独立的 PID、虚拟地址空间和文件描述符表实现进程隔离。默认情况下,一个进程无法直接访问另一个进程的内存或对象,必须借助跨进程通信(IPC)机制来完成数据交换和任务调度。

Android 选用 Binder 作为主要的 IPC 方案,其设计同时兼顾性能、安全性和易用性。

1.1 IPC 方式对比

通信方式拷贝次数安全模型典型场景
共享内存0 次(需同步)无内置权限校验大量数据传递
Socket / Pipe2 次依赖 UID 校验网络通信、本地流数据传输
Binder1 次基于 UID/PID + 自描述接口系统服务调用、跨进程任务调度

1.2 Binder 驱动——内核中的 IPC 枢纽

Binder 驱动(位于 Linux 内核 /dev/binder)是 Binder IPC 的核心。它负责:

  • 管理进程间的 Binder 实体(binder_node)和代理(binder_ref)。
  • 执行内存映射(mmap),实现一次数据拷贝的零拷贝传输(实际为一次用户态拷贝)。
  • 维护线程池与等待队列,处理同步/异步调用的事务。
  • 执行安全校验(检查调用方 UID/PID 和服务可访问权限)。

1.3 Client / Server / ServiceManager 三员角色

一次典型的 Binder 跨进程调用涉及三个参与者:

  • Client:发起请求的进程(例如 Launcher 查询包管理器服务)。
  • Server:实现具体服务的进程(例如 system_server 中的 PackageManagerService)。
  • ServiceManager:系统级名服务,负责管理与查询服务注册表。Server 在启动时向 SM 注册服务名和 Binder 引用;Client 通过 SM 查找服务后获得代理,从而直接与 Server 通信。

三者通过 Binder 驱动完成发现、连接与调用。

Binder 通信模型 Client (发起请求的进程) Server (实现服务的进程) ServiceManager (服务注册/查询) Binder 驱动 (内核层) ① 注册服务 ② 查询服务 ③ 返回代理 ④ 跨进程方法调用 模型要点: 客户端与服务端不直接交换句柄,而是通过 Binder 驱动中转数据;ServiceManager 只在服务发现阶段参与,后续调用路径为 C→D→S。

1.4 服务注册与发现流程

  1. Server 启动:调用 addService(name, binder) 向 ServiceManager 注册服务名和对应 Binder 引用。
  2. ServiceManager 存储:在内部哈希表中维护 服务名 → Binder 引用 映射。
  3. Client 查询:调用 getService(name),通过 Binder 驱动向 ServiceManager 发送查询请求。
  4. 获取代理:ServiceManager 返回服务的 Binder 代理(BpBinder)。Client 通过代理发起后续调用。
  5. 方法调用:Client 调用代理上的接口方法,实际通过 transact() 写入 Binder 驱动,驱动唤醒 Server 的线程池执行回调。

1.5 关键 API 与命令

// 注册服务(Java 层示例)
ServiceManager.addService("my_service", new MyBinder());

// 查询服务(Java 层示例)
IBinder binder = ServiceManager.getService("my_service");
IMyService service = IMyService.Stub.asInterface(binder);

// 通过 adb 查看已注册的服务
adb shell service list

// 查看 Binder 驱动设备节点
adb shell ls -l /dev/binder

🛠 调试提示:使用 adb shell dumpsys servicemanager 可以查看所有已注册服务的详细统计(引用计数、调用次数)。如果发现服务未注册,通常是因为 Server 进程崩溃或权限不足。

⚠️ 常见错误

  • “Service not found”:Client 查询时服务名拼写错误,或 Server 尚未启动。
  • “Binder transaction failed”:数据大小超过 1MB 阈值(BINDER_MAX_TRANSACTION_SIZE),应拆分数据或改用文件描述符传递。
  • “DeadObjectException”:Server 端进程已死亡,Client 持有过期的代理,需重新获取。

1.6 性能与稳定性要点

  • Binder 线程池:默认每个进程有 16 个 Binder 线程(可配置),当并发请求过多时应合理调整 BinderInternal.setMaxThreads()。
  • 同步/异步选择:耗时操作应使用 oneway 关键字声明异步调用,避免阻塞 Client 主线程。
  • 稳定性设计:Server 端必须妥善处理 linkToDeath,在 Client 死亡后释放资源;Client 应监听 DeathRecipient 以优雅降级。
  • 电量与调度:Binder 调用可利用内核 wakelock 机制,但过度频繁的跨进程通信会增加功耗,建议合并请求或使用内存缓存。