学 Android 跨进程 IPCAIDL、Binder、Stub、Proxy、DMA-BUF 一堆概念经常把人绕晕。很多同学死记硬背「服务端 Stub客户端 Proxy」一碰到回调场景直接逻辑混乱。本文抛开晦涩内核源码用「快递系统」生活化类比讲清楚Stub / Proxy 角色为什么可以互换到底怎么判断IBinder 句柄到底怎么传递给对方进程mmap 一次拷贝原理以及 Binder 1M 大小限制的坑DMA-BUF 解决大数据传输的真实原理以及常见误区带回调的 AIDL 完整通信流程把面试高频坑点一次性捋明白看完就能讲给别人听。关键词Android IPCBinderAIDLStub ProxyDMA-BUFmmap跨进程通信目录前言AIDL 和 Binder 到底是什么Stub Proxy最大误区角色不是写死的IBinder 句柄的两种传递方式Binder 底层 mmap一次拷贝与 1M 大小坑DMA-BUF大件货物别塞 Binder 快递柜完整流程串讲带回调的 AIDL 通信全过程面试速记总结前言搞 Android IPC 的时候是不是看到一堆名词直接脑壳疼Binder、AIDL、Stub、Proxy、IBinder、mmap、DMA-BUF……网上很多文章一上来就甩内核函数、ioctl 源码越看越迷糊。很多人脑子里固化一个认知Service 服务端一定是 StubApp 客户端一定是 Proxy。结果一碰到 AIDL 回调场景直接翻车哎怎么客户端这边也 new 出来 Stub 了看完这篇文章把 Binder 整套机制类比成现实里的寄快递很多想不通的点瞬间通透。AIDL 和 Binder 到底是什么用快递系统打比方BinderAndroid 内核提供的跨进程通信驱动可以理解成城市快递运输系统。Android 进程之间内存严格隔离A 进程不能直接访问 B 进程的对象所有跨进程消息都要走 Binder 这套「快递系统」流转。AIDL接口定义文件相当于快递单据模板。我们写.aidl文件定义接口名、方法名、入参出参。编译阶段 SDK 自动帮我们生成 Java/Kotlin 代码产出 Stub 和 Proxy 类不用我们手写底层 IPC 逻辑。⚠️ 重要踩坑提醒通信两端的 aidl 文件包名、接口名、方法顺序、参数类型必须完全一致。好比收发快递两方快递单格式不一样快递系统解析失败直接发生调用错乱、DeadObjectException。Stub Proxy最大误区角色不是写死的 核心金句Stub 干活打工人真实实体对象Proxy 跑腿下单小哥代理替身。谁干活谁是 Stub谁发起调用谁是 Proxy。角色跟随接口变化进程身份不会永久绑定❌ 错误认知Service 进程永远是 StubClient 客户端永远是 Proxy。✅ 我们用两个生活场景一看就懂场景 1客户端调用服务接口Client → MasterMaster 服务进程实现IMasterService.Stub()这就是Stub 打工人业务方法真正在这里执行。Client 进程拿到代理对象调用proxy.sendMessage()这个就是Proxy 跑腿小哥。小哥把方法参数打包成 Parcel 包裹丢进 Binder 快递系统跨进程送到 Master 的 Stub 手里执行业务逻辑。Client【Proxy 跑腿小哥】 → 发送消息 → Master【Stub 打工人干活】场景 2回调服务反过来通知客户端Master → Client现在 Master 收到消息需要反过来通知 Client。这时候Client 自己创建一份IMessageCallback.Stub()这就是 Client 这边的打工人 Stub。然后把这个 Stub 的 IBinder「联系方式」当做参数通过快递传给 Master 进程。Master 拿到这份联系方式生成 Proxy 跑腿小哥主动发起调用回调逻辑运行在 Client 进程。Master【Proxy 跑腿小哥】 → 回调通知 → Client【Stub 打工人干活】 看到没有同一个 Client 进程身份是会切换的调用 Master 接口的时候Client 是 Proxy发起调用接收回调接口的时候Client 变成 Stub执行方法判断 Stub/Proxy 面试口诀直接背谁object : Xxx.Stub()/new Xxx.Stub()谁就是这份 AIDL 接口的Stub干活实体谁调用Xxx.Stub.asInterface(binder)拿到对象然后调用接口方法谁就是Proxy发起调用方法体跑在哪个进程哪个进程就是 Stub 所在进程。IBinder 句柄的两种传递方式划重点光 new 一个 Stub 对象没用你不把 IBinder 句柄相当于打工人的联系方式传递给对方进程别人根本找不到你完全无法发起 IPC 调用。投递 IBinder 句柄有两种主流途径方式 1系统 AMS 帮你投递bindService 路径这是大家最熟悉的方式Master 服务里onBind()方法 return 我们的 Stub 对象把 Binder 实体交给 AMS 系统。Client 调用bindService()AMS 作为中间驿站把这份 Binder 句柄投递到 Client 进程。Client 侧回调onServiceConnected(name, service: IBinder)参数里的service就是对方 Stub 在本进程的句柄。Client 调用IMasterService.Stub.asInterface(service)包装生成 Proxy 代理对象。局限这个通道只能拿到 Service 端onBind返回的那一个 Binder 对象。想把客户端自己的 Callback Stub 传给服务端靠这个做不到。方式 2手动当做参数传递AIDL 方法传参回调核心Callback 回调场景就是用这种方式Client 本地object : IMessageCallback.Stub()创建自己的 Stub 实体。Client 已经持有IMasterService的 Proxy调用proxy.registerCallback(callbackStub)。本质就是把 callbackStub 这个 IBinder 句柄作为 AIDL 方法的入参通过 Binder 驱动跨进程发送给 Master。Master 进程收到入参拿到的是 IBinder 句柄调用IMessageCallback.Stub.asInterface(cb)生成对应 Proxy。之后 Master 调用proxy.onMessage()就能反向跨进程通知 Client。一句话总结第一种是系统帮你传服务端的 Binder第二种是你自己通过 AIDL 参数传客户端的 Binder 给对方。Binder 底层 mmap一次拷贝与 1M 大小坑很多人都听过 Binder 是「一次拷贝」比管道、socket 的两次拷贝效率高。这背后就是 mmap 在起作用。什么是 mmap还是用仓库打比方接收方进程提前调用 mmap开辟一块 Binder 缓冲区。同一块物理内存同时映射到「内核虚拟地址」和「接收方用户虚拟地址」。相当于在收货人家门口建了一个共享仓库内核和用户都能直接开门取货。传统 IPC vs Binder传统管道 /socket两次拷贝发送方把数据从用户空间拷贝到内核缓冲区内核再把数据从内核缓冲区拷贝到接收方用户空间 两次 CPU 内存拷贝Binder一次拷贝发送方调用copy_from_user把数据从自己用户空间拷贝到接收方提前 mmap 好的共享内核缓冲区接收方因为已经 mmap 映射了这块内存用户态直接读取不需要第二次拷贝 仅一次 CPU 内存拷贝必踩的坑缓冲区有大小限制每个普通 App 进程的 Binder 缓冲区默认大小是1M - 8KB而且是进程全局共享所有 Binder 事务共用。如果你一次 Parcel 序列化的数据太大仓库塞不下直接抛出TransactionTooLargeException。好比快递柜就这么大你硬塞一个大行李箱肯定塞不进去。所以 Binder 天生适合小而频繁的 RPC 调用不适合传大图、视频这种大块二进制数据。DMA-BUF大件货物别塞 Binder 快递柜既然 Binder 快递柜装不下大件那跨进程传 Camera 帧、Bitmap 大图怎么办答案就是 DMA-BUF。先纠正两个高频误区❌ 误区 1DMA-BUF 是另一种 IPC 机制✅ 真相DMA-BUF 是 Linux 内核的共享内存缓冲区框架不是 IPC。Binder 在这里只负责传递它的「提货码」。❌ 误区 2DMA-BUF 无限大完全不经过 CPU✅ 真相DMA-BUF 依然受整机物理内存限制DMA 只是硬件搬运数据内存分配、缓存同步、生命周期管理还是 CPU 负责。正确玩法只传提货码不传货发送方分配一块 DMA-BUF 物理内存把图片 / 视频像素数据放进去。Camera、GPU、编解码器等硬件可以通过 DMA 硬件直接读写这块内存绕开 CPU 做内存拷贝。Binder 只传递这块内存的文件描述符 fd提货码。fd 只是一个 int 数字非常小完全不受 Binder 1M 缓冲区限制。接收进程拿到 fd自己 mmap 映射这块物理内存两个进程访问同一块物理内存像素数据完全不走 Binder 拷贝路径。对比小结表格方案玩法大小限制适用场景Binder 直接传 Parcel真实数据走 Binder 缓冲区受 1M-8KB 限制大数据直接崩普通 RPC字符串、简单实体Binder 传 DMA-BUF 的 fd只传提货码 fd真实数据放共享内存不受 Binder 限制受物理内存限制Camera 帧、Bitmap、视频帧等大块数据补充老的 Ashmem 匿名共享内存同样是 mmap 共享内存但不支持 DMA 硬件访问只能 CPU 读写适合普通大块数据不能给 GPU、摄像硬件直接使用。完整流程串讲带回调的 AIDL 通信全过程我们把整个流程串一遍彻底理清角色互换和两次跨进程Master 服务端创建IMasterService.Stub打工人onBind交给 AMS 系统。Client 调用bindServiceonServiceConnected拿到 IBinder 句柄生成IMasterServiceProxy跑腿小哥。此时 Client 可以调用sendMessageIPC 跑到 Master Stub 执行。Client 本地新建IMessageCallback.Stub另一个打工人专门收回调。Client 调用proxy.registerCallback(callbackStub)把 callback 的 IBinder 联系方式通过 AIDL 参数传给 Master。Master 收到 IBinder 句柄生成IMessageCallbackProxy跑腿小哥。Master 需要广播消息时调用callbackProxy.onMessage()IPC 跨进程回到 ClientClient 的 Stub 打工人执行回调逻辑。一次用户点击发送背后发生了两次跨进程 IPC第 1 跳Client → Master发消息第 2 跳Master → Client回调回显好了就写到这主要是给自己加深下印象这块我也要不断的去深入学习。