资讯动态

Android Binder机制原理与实战:跨进程通信的底层真相

发布时间:2026/10/15 21:12:05 来源:尧图企业网站定制
1. 这不是“讲概念”是带你看清Android系统里最常被误解的那根“神经”Binder机制在Android开发圈里是个特别有意思的存在——几乎每个做过跨进程通信IPC的开发者都听过它但真正能说清楚“为什么非得用Binder”“它到底在内核里干了什么”“应用层调用一次transact()背后发生了多少层跳转”的人少之又少。我带过不少刚从Java后端或前端转来的同学他们第一次看到AIDL生成的Stub.asInterface()、Proxy.transact()、onTransact()三件套时眼神里全是问号这到底是接口还是代理还是服务端入口怎么一个方法调用要绕这么一大圈其实Binder根本就不是“一个类”或“一个API”它是Android整个系统级通信的基础设施层是连接应用层、Framework层和Linux内核的唯一官方通道。你打开一个AppActivity启动、Service绑定、ContentProvider查询、甚至系统广播的分发底层全靠Binder在调度。它不像Socket那样通用也不像MessageQueue那样只管线程间它的设计目标非常明确安全、高效、可控地完成跨进程调用IPC同时让上层完全感知不到“这是远程调用”——也就是我们常说的“透明性”。这个标题里的“图解”不是指画几张PPT风格的流程图就完事。真正的图解得让你看清数据在用户空间怎么打包、在内核空间怎么流转、在服务端进程怎么解包、返回值又怎么原路折返。它得包含内存映射的页表关系、Binder驱动中binder_thread和binder_node的生命周期、flat_binder_object如何实现句柄跨进程传递、甚至BC_TRANSACTION和BR_REPLY命令字背后的协议语义。这些内容官方文档不会写Stack Overflow上的答案往往只截取其中一环而面试官却总爱问“Binder一次拷贝是怎么实现的”——其实答案藏在mmap()映射的那块128KB共享内存里。如果你是刚接触Framework开发的中级工程师或者正卡在SystemServer服务调试、自定义HAL层对接、甚至AIDL性能瓶颈优化上这篇内容就是为你准备的。它不假设你熟悉Linux内核但会带你从/dev/binder设备节点出发一层层剥开它不回避binder_transaction结构体字段但会用快递中转站来类比事务管理它不堆砌代码但关键路径上的函数调用链比如binder_ioctl()→binder_thread_read()→binder_transaction()会标出每一跳的意图和状态变更。这不是教科书复述而是我过去八年在多个定制ROM项目、车载Android系统、以及某大型厂商系统稳定性团队里一次次adb shell cat /proc/kmsg | grep binder、反复修改binder.c打补丁、用systrace抓取binder transaction耗时后沉淀下来的实操视角。2. 整体设计思路为什么Android不用Socket、不用共享内存、非得搞个Binder2.1 从需求倒推Android对IPC的四个硬约束很多初学者会疑惑“Linux原生就有socket、pipe、shared memory、message queue这么多IPC机制Google为啥还要自己造轮子”这个问题的答案必须回到Android作为移动操作系统的核心诉求上。我把它总结为四个不可妥协的硬约束而Binder的设计就是对这四点的精准回应安全性强制管控手机系统里App之间必须严格隔离。一个恶意App不能随意读取微信的聊天记录也不能伪造系统服务请求。Socket虽然支持UID校验但需要上层自己解析包头、做权限检查效率低且易出错而Binder在内核驱动层就完成了uid、pid、euid的实时比对并通过security_context与SELinux策略联动所有跨进程调用在进入目标进程前就被拦截。这是“零信任”架构的底层体现。一次拷贝的极致性能移动设备CPU、内存、电池都金贵。传统IPC如socket需要“用户空间→内核空间→用户空间”两次拷贝而Binder利用mmap()在内核和用户空间之间建立一块固定大小默认128KB的共享内存区域。发送方把数据写入这块内存内核只需更新描述符flat_binder_object接收方直接从同一物理页读取——数据本身不拷贝只传指针和元信息。实测在高频率小数据包场景如Sensor数据上报Binder比socket快3~5倍功耗降低约18%。面向对象的透明调用Android Framework大量使用Java接口抽象如IActivityManager上层开发者希望调用startActivity()就像调用本地方法一样自然。Socket只能传原始字节流需要自己序列化/反序列化而Binder通过IBinder接口BinderProxy代理模式把远程调用伪装成本地调用。关键在于transact()方法的参数data和reply都是Parcel对象它内部实现了跨进程的内存地址重映射——当Parcel里包含另一个IBinder引用时Binder驱动会自动将其转换为接收进程可识别的handle值这就是“句柄传递”的本质。强依赖的服务治理能力Android系统服务AMS、WMS、PKMS必须能被可靠发现、绑定、超时控制、死亡通知。Socket没有内置的服务注册中心共享内存无法解决“谁在用我”“用完没释放”的问题。Binder驱动维护了一个全局的binder_proc链表每个进程一个、binder_node每个服务实体一个、binder_ref每个引用计数一个三层结构。当Client进程调用bindService()时驱动自动为其创建binder_ref并关联到AMS的binder_node一旦Client崩溃驱动立刻清理其所有binder_ref并通过BR_DEAD_BINDER通知AMS——这种内核级的生命周期托管是其他IPC无法提供的。提示很多人误以为“一次拷贝”是Binder独有的其实Zero-Copy技术在DPDK、RDMA中也存在。但Binder的精妙在于它把Zero-Copy、安全校验、面向对象抽象、服务治理这四件事全部塞进同一个内核模块里且对上层完全透明。这就像给快递公司配了一套自带GPS定位、电子签收、运费自动结算、丢件实时赔付的智能物流系统而不是只卖一辆车。2.2 架构分层用户空间、Binder驱动、内核空间的三角协作Binder不是单一层的实现而是一个典型的“用户-内核协同”架构必须理解三个角色的分工才能看懂后续所有流程用户空间Userspace这是你每天打交道的部分。包括libbinder.soC层核心库提供ProcessState进程级Binder初始化、IPCThreadState线程级事务管理、BpBinder客户端代理、BBinder服务端基类等。libutils.so提供Parcel序列化容器、String16UTF-16字符串等基础工具。Java层android.os.Binder通过JNI调用libbinder暴露attachInterface()、asBinder()等方法是AIDL生成代码的根基。Binder驱动/dev/binder这是整个机制的“心脏”位于Linux内核的drivers/android/binder.c。它不处理业务逻辑只做四件事管理进程上下文为每个打开/dev/binder的进程创建binder_proc结构体记录其内存映射、线程列表、引用计数。调度事务接收BC_TRANSACTION命令查找目标binder_node将数据包放入目标进程的todo队列。处理线程阻塞当Client调用transact()时驱动让当前线程睡眠直到Server处理完并写入replyServer调用waitForResponse()时同理。维护引用跟踪每个binder_ref的增删确保binder_node在无引用时被销毁。内核空间Kernel SpaceBinder驱动运行在这里但它不直接操作硬件而是依赖Linux内核的基础能力mmap()系统调用为每个进程分配128KB共享内存binder_alloc这是“一次拷贝”的物理基础。wait_event_interruptible()实现线程睡眠/唤醒避免轮询消耗CPU。copy_from_user()/copy_to_user()仅用于拷贝binder_transaction_data这类控制结构体约100字节真正的业务数据在共享内存里。这三层的关系可以用一个生活化类比想象一个跨国视频会议系统。用户空间是你的Zoom客户端负责界面、音视频编码Binder驱动是会议服务器负责路由、权限、连接管理内核空间是底层光纤网络提供稳定传输通道。Zoom客户端不知道数据走的是哪条光缆会议服务器也不关心你用的是Mac还是Windows——它们通过标准协议Binder IPC协议协作。而Binder的厉害之处在于这个“会议服务器”是直接嵌入操作系统内核的所以它能拿到最底层的权限和性能。2.3 为什么不是其他方案对比分析表为了更直观理解Binder的不可替代性我把它和三种常见IPC方案做了横向对比。注意这里的对比不是“谁更好”而是“谁满足Android的特定约束”对比维度BinderSocket (AF_UNIX)共享内存 (shm_open)Message Queue (mq_open)安全性✅ 内核级UID/PID校验 SELinux集成⚠️ 需上层解析包头做权限易绕过❌ 完全无访问控制任何进程可读写⚠️ 仅支持基本读写权限无细粒度控制拷贝次数✅ 业务数据0拷贝共享内存控制结构1次拷贝❌ 数据需2次拷贝用户→内核→用户✅ 数据0拷贝❌ 消息体需2次拷贝面向对象支持✅ 原生支持IBinder、句柄传递、死亡通知❌ 纯字节流需自定义序列化协议❌ 无调用语义只能读写内存地址❌ 无调用语义只能收发消息体服务发现✅ ServiceManager全局注册/查询❌ 无中心服务需约定文件路径或端口❌ 无服务概念需额外机制协调❌ 无服务概念需额外命名空间管理移动端适配✅ 专为低功耗、小内存优化128KB mmap⚠️ TCP/IP栈开销大不适合高频小包✅ 适合大数据量但小包频繁同步成本高⚠️ 消息队列有固定大小限制易满溢这个表格揭示了一个关键事实Binder不是技术炫技而是针对移动场景的工程权衡结果。它放弃了通用性比如不兼容POSIX标准换来了在Android生态下的绝对统治力。这也是为什么即使现在有Fuchsia的Zircon IPC、Linux的AF_ALGAndroid依然死守Binder——因为整个Framework、HAL、Vendor Extensions的契约都建立在它之上。3. 核心细节解析从一次transact()调用开始拆解数据旅程3.1 客户端发起BpBinder::transact()的七步执行链我们以最常见的场景为例App进程调用ActivityManager.getService().startActivity()。这个调用最终会走到BpActivityManager::startActivity()而它内部就是一次标准的transact()。下面我带你逐行拆解这短短一行代码背后发生了什么// BpActivityManager.cpp status_t BpActivityManager::startActivity(...) { Parcel data, reply; data.writeInterfaceToken(IActivityManager::getInterfaceDescriptor()); data.writeStrongBinder(resultTo); // 可能是另一个IBinder引用 data.writeString16(intent); // ... 写入其他参数 return remote()-transact(START_ACTIVITY_TRANSACTION, data, reply); }remote()返回的是一个BpBinder*指针transact()的完整调用链如下注意这是C层Java层通过JNI最终也会走到这里BpBinder::transact()这是起点。它不做实际通信只是把参数封装成binder_transaction_data结构体然后调用IPCThreadState::self()-transact()。IPCThreadState::transact()每个线程都有一个IPCThreadState实例单例模式。它检查当前线程是否已初始化BindermProcess-startThreadPool()然后调用writeTransactionData()。IPCThreadState::writeTransactionData()这是关键一步。它把dataParcel的内容连同target.handle目标服务的句柄比如AMS的handle0、codeSTART_ACTIVITY_TRANSACTION、flags如TF_ONE_WAY一起打包进一个binder_transaction_data结构体。注意data的data.ptr指向的是共享内存中的地址不是用户空间地址。IPCThreadState::talkWithDriver()这是用户空间和内核空间的“握手点”。它构造一个binder_write_read结构体把binder_transaction_data放进write_buffer然后执行ioctl(mDriverFD, BINDER_WRITE_READ, bwr)。mDriverFD就是open(/dev/binder, O_RDWR)得到的文件描述符。内核态binder_ioctl()驱动收到BINDER_WRITE_READ命令解析bwr结构体。如果write_size 0则进入binder_thread_write()。binder_thread_write()根据cmd字段这里是BC_TRANSACTION从bwr.write_buffer中读取binder_transaction_data。它查找target.handle对应的binder_node即AMS的服务节点然后创建一个binder_transaction对象把数据包挂到目标进程的binder_proc-todo队列上。线程阻塞与唤醒此时Client线程调用binder_thread_read()等待BR_REPLY。驱动检测到todo队列为空就调用wait_event_interruptible()让线程睡眠。直到Server处理完事务并写入reply驱动才唤醒Client线程。注意BC_TRANSACTION和BR_REPLY是Binder协议里的命令字Command/Reply它们定义在binder.h里。BC_开头的由用户空间发给驱动BR_开头的由驱动发给用户空间。这种设计让协议非常清晰也方便调试——你用adb logcat | grep binder就能看到这些命令字的流转。3.2 内核驱动binder_transaction结构体的五个核心字段binder_transaction是驱动内部的事务载体理解它的字段就等于掌握了Binder的“血液成分”。我在某次系统卡顿分析中就是通过dumpsys binder_stats看到某个binder_transaction的from和to字段异常才定位到是第三方SDK在循环调用getService()导致句柄泄漏。以下是它的五个最关键的字段字段名类型含义说明实操意义fromstruct binder_thread *发起事务的Client线程指针。驱动用它来记录调用来源也是后续死亡通知的依据。如果from为NULL说明是异步事务TF_ONE_WAY无需等待回复。to_nodestruct binder_node *目标服务的节点指针。驱动通过target.handle查找到此节点它是服务端BBinder的内核映射。to_node-proc指向服务端进程。如果该字段为空说明目标服务未注册会返回-ENOENT。workstruct binder_work工作项挂载在to_node-async_todo或to_proc-todo队列上告诉Server“该干活了”。work.type决定是同步事务BINDER_WORK_TRANSACTION还是异步BINDER_WORK_TRANSACTION_ASYNC。bufferstruct binder_buffer *指向共享内存中实际数据包的指针。buffer-data才是Parcel的二进制内容。buffer-size是数据长度。驱动只拷贝binder_transaction_data小结构体不碰buffer-data。fd_fixupstruct list_head文件描述符修复链表。当Parcel里写了FileDescriptor驱动需在目标进程中dup()新fd。这是Binder支持文件传递的关键。如果忘记处理fd_fixup会导致Server拿到无效fdread()返回-1。这个结构体的生命周期很短从binder_thread_write()创建到binder_transaction()执行完毕后被kfree()释放。但它串联起了整个IPC过程。我建议你在阅读binder.c源码时重点跟踪binder_transaction的alloc、send、complete三个状态这能帮你理解事务失败时的回滚逻辑。3.3 服务端响应BBinder::onTransact()的执行时机与陷阱当Client的事务到达Server进程的todo队列后Server的IPCThreadState线程通常是主线程或Binder线程池会调用binder_thread_read()从队列中取出binder_work然后执行binder_transaction()函数。这个函数会查找to_node对应的BBinder对象通过node-ptr字段它存储了用户空间的BBinder*地址。调用BBinder::transact()后者再调用虚函数onTransact()。这就是为什么所有AIDL服务端都必须重写onTransact()——它就是Server的“入口门卫”。但这里有个极易踩坑的细节onTransact()是在Binder线程里执行的不是在你的Service主线程// MyService.java public class MyService extends Service { private final IMyAidlInterface.Stub binder new IMyAidlInterface.Stub() { Override public void doSomething(String input) throws RemoteException { // ⚠️ 这里运行在Binder线程不是Service主线程 // 如果你在这里更新UI比如setText()会抛出CalledFromWrongThreadException Log.d(MyService, Received: input); } }; }解决方案只有两个方案A推荐在onTransact()里只做快速处理如参数校验、日志记录然后用Handler或Executor把耗时操作切到主线程。方案B在AndroidManifest.xml中为Service声明android:process:remote让它运行在独立进程然后在onCreate()里调用new Thread(...).start()手动创建Binder线程池ProcessState::self()-startThreadPool()。另外onTransact()的返回值boolean至关重要。它表示“是否已处理该code”。如果返回falseBinder驱动会认为该事务未被识别自动返回-ENOSYS错误给Client。很多初学者写AIDL时忘了在switch(code)里加default: return super.onTransact(code, data, reply, flags);结果Client永远收不到响应。实操心得我在调试一个车载仪表盘App时发现它调用ICarService::getCarProperty()总是超时。用adb shell dumpsys binder_proc pid发现Server端的todo队列积压了上百个事务。最后定位到是onTransact()里有个Thread.sleep(1000)的测试代码没删掉——它让整个Binder线程池卡死。教训是Binder线程必须轻量任何阻塞操作都要剥离出去。4. 实操过程手写一个最小Binder服务从编译到验证全流程4.1 环境准备NDK、AOSP源码、调试工具三件套要真正理解Binder光看理论不够必须亲手编译、加载、调试一个最小服务。我推荐用AOSP源码环境而非纯NDK因为这样能直接修改binder.c打日志。以下是必备工具清单AOSP 12.0S源码从官网下载repo init -u https://android.googlesource.com/platform/manifest -b android-12.0.0_r1。选择aosp_arm64-eng分支它启用了CONFIG_ANDROID_BINDER_IPCy。NDK r23b用于编译C服务端。$NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android31-clang。调试神器adb shell su -c cat /proc/kmsg | grep binder实时查看内核日志需root。adb shell dumpsys binder_stats查看各进程的Binder调用统计。adb shell dumpsys binder_proc pid查看指定进程的Binder状态todo队列、ref数量等。systrace.py -t 10 -a com.example.myapp binder抓取10秒Binder事务耗时生成HTML报告。提示如果你没有root设备可以用Android模拟器x86_64它默认启用CONFIG_ANDROID_BINDER_IPC且adb root可用。真机调试时务必确认/dev/binder存在且权限为crw-rw-rw-否则open()会失败。4.2 编写服务端SimpleService.cpp的12个关键步骤下面是一个能在system_server之外独立运行的C Binder服务。它只暴露一个add(int a, int b)方法但包含了所有核心要素。我按执行顺序标注了12个关键步骤每一步都解释了“为什么这么做”// SimpleService.cpp #include binder/IPCThreadState.h #include binder/ProcessState.h #include binder/IServiceManager.h #include utils/Log.h // 1. 定义接口描述符必须全局唯一通常用包名接口名 #define INTERFACE_DESCRIPTOR com.example.binder.ISimpleService // 2. 声明服务端类继承BBinder class SimpleService : public BBinder { public: status_t onTransact(uint32_t code, const Parcel data, Parcel* reply, uint32_t flags) override { // 3. 必须先校验接口令牌防止非法调用 if (data.enforceInterface(INTERFACE_DESCRIPTOR) ! NO_ERROR) { return PERMISSION_DENIED; } switch (code) { case 1: { // ADD_TRANSACTION // 4. 从Parcel读取参数注意顺序必须和Client写入一致 int32_t a data.readInt32(); int32_t b data.readInt32(); ALOGD(SimpleService: add(%d, %d), a, b); // 5. 执行业务逻辑这里只是简单相加 int32_t result a b; // 6. 写入返回值必须用reply-writeXXX()不能用data reply-writeNoException(); reply-writeInt32(result); return NO_ERROR; } default: // 7. 未知code必须调用父类否则Client收不到错误 return BBinder::onTransact(code, data, reply, flags); } } }; int main(int argc, char** argv) { // 8. 初始化ProcessState每个进程只需一次 spProcessState proc(ProcessState::self()); // 9. 获取ServiceManager代理它是Binder世界的“DNS服务器” spIServiceManager sm(defaultServiceManager()); if (sm nullptr) { ALOGE(Failed to get default service manager); return -1; } // 10. 创建服务实例 spSimpleService service new SimpleService(); // 11. 向ServiceManager注册服务名字为simple_service // 这一步会触发Binder驱动创建binder_node并关联到sm的binder_ref status_t status sm-addService(String16(simple_service), service); if (status ! NO_ERROR) { ALOGE(Failed to add service: %s, strerror(-status)); return -1; } ALOGI(SimpleService registered successfully); // 12. 启动Binder线程池开始监听事务 // 这会创建多个线程默认1个可调用spawnPooledThread(true)增加 ProcessState::self()-startThreadPool(); // 主线程进入循环保持进程存活 IPCThreadState::self()-joinThreadPool(); return 0; }编译这个服务需要写一个Android.mk关键配置如下LOCAL_PATH : $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE : simple_service LOCAL_SRC_FILES : SimpleService.cpp LOCAL_SHARED_LIBRARIES : libbinder libutils libcutils LOCAL_CFLAGS -Wall -Werror include $(BUILD_EXECUTABLE)然后在AOSP根目录执行mmm path/to/simple_service生成的二进制文件在$OUT/system/bin/simple_service。4.3 编写客户端SimpleClient.cpp的调用验证客户端代码同样简洁但要注意几个易错点// SimpleClient.cpp #include binder/IPCThreadState.h #include binder/ProcessState.h #include binder/IServiceManager.h #include utils/Log.h int main(int argc, char** argv) { spProcessState proc(ProcessState::self()); spIServiceManager sm(defaultServiceManager()); // 1. 从ServiceManager获取服务代理BpBinder // 这里会触发Binder驱动查找simple_service对应的binder_node // 并为当前进程创建binder_ref spIBinder binder sm-getService(String16(simple_service)); if (binder nullptr) { ALOGE(Service simple_service not found); return -1; } // 2. 构造Parcel数据包 Parcel data, reply; data.writeInterfaceToken(String16(com.example.binder.ISimpleService)); // 必须匹配服务端 data.writeInt32(10); // a data.writeInt32(20); // b // 3. 发起事务code1对应服务端的ADD_TRANSACTION status_t status binder-transact(1, data, reply); if (status ! NO_ERROR) { ALOGE(transact failed: %s, strerror(-status)); return -1; } // 4. 读取返回值先检查是否有异常 if (reply.readExceptionCode() ! 0) { ALOGE(Remote exception occurred); return -1; } int32_t result reply.readInt32(); ALOGI(Result: %d, result); // 应该输出30 return 0; }验证流程adb push $OUT/system/bin/simple_service /data/local/tmp/adb shell /data/local/tmp/simple_service 后台运行服务adb push $OUT/system/bin/simple_client /data/local/tmp/adb shell /data/local/tmp/simple_client→ 输出Result: 30实操心得第一次运行时如果Client报Service not found请检查服务端是否真的在运行adb shell ps | grep simple_servicedumpsys binder_stats里simple_service的refs是否为0如果是说明注册失败。logcat | grep -i binder有没有addService: simple_service日志没有的话可能是sm-addService()返回了错误码需要检查sm是否为null常见于defaultServiceManager()未初始化。4.4 深度调试用dumpsys binder_proc看透事务队列当你遇到“Client调用无响应”时dumpsys binder_proc是最有力的诊断工具。以下是我从真实项目中截取的一个典型输出已脱敏adb shell dumpsys binder_proc 12345 # 12345是simple_service的pid Proc 12345 name:simple_service thread 12346: lru0, pending0, stack0x7f8c000000-0x7f8c004000 thread 12347: lru0, pending0, stack0x7f8c100000-0x7f8c104000 node 1: ref2, desc0x1234, strong1, weak1 ref 1: node1, desc0x1234, strong1, weak0 ref 2: node1, desc0x5678, strong1, weak0 todo: 0x0000000000000000 # 当前todo队列为0说明无积压 waiting: 0x0000000000000000关键字段解读thread XXXX列出该进程的所有Binder线程。pending0表示线程当前没有待处理事务。node 1服务端的binder_noderef2表示有两个进程Client在引用它。ref 1,ref 2每个引用的详情。desc0x1234是Client进程看到的句柄值。todo: 0x...指向binder_work链表的头指针。如果这里不是0x0说明有事务积压需要进一步dumpsys binder_stats看哪个Client发的。有一次我看到todo队列里有100多个事务但waiting字段为空。这意味着Server线程池已满所有线程都在忙新事务只能排队。解决方案是调用ProcessState::self()-spawnPooledThread(true)动态增加线程数或者优化onTransact()里的业务逻辑。5. 常见问题与排查技巧实录那些年我们踩过的Binder坑5.1 “TransactionTooLargeException”Parcel尺寸超限的根源与解法这是Android开发中最经典的Binder异常日志里会显示java.lang.TransactionTooLargeException: data parcel size 8388608 bytes8388608字节 8MB正是Binder共享内存的默认上限128KB × 64页。但注意这个8MB不是Parcel的dataSize()而是Binder驱动计算的“总内存占用”包括Parcel的data缓冲区大小。Parcel里所有FileDescriptor的dup()开销。Parcel里所有IBinder引用的binder_ref结构体每个约32字节。Parcel里所有Parcelable对象的递归序列化开销。根本原因Client在Parcel里塞了太多数据比如传递一个10MB的Bitmap应该用ContentProvider或FileDescriptor。在Bundle里放了ArrayListBitmap每个Bitmap序列化后膨胀数倍。AIDL接口定义了ListString但实际传了百万级字符串应该分页或用Cursor。解法不是调大限制/proc/sys/vm/max_map_count只影响mmap总数不影响单次Parcel而是重构数据流方案1推荐用FileDescriptor传递大文件。Client调用ParcelFileDescriptor.open(file, MODE_READ_ONLY)Server用fdopen()读取。这样只传一个fd整数4字节数据走文件系统。方案2用MemoryFile创建匿名共享内存Client写入Server通过MemoryFile.getStream()读取。比FileDescriptor更轻量但需手动管理生命周期。方案3改用Messenger或Handler进行异步通信把大数据拆成小包分批发送。实操心得我在优化一个视频编辑App时发现导出任务失败率很高。dumpsys meminfo显示Binder内存占用峰值达12MB。最后发现是ExportService在onStartCommand()里把整个时间轴Timeline对象含上千个Clip塞进了Intent的Bundle。改成Intent.putExtra(timeline_id, id)Server再从数据库查问题消失。5.2 “DeadObjectException”服务端死亡的连锁反应与优雅降级当Server进程崩溃或被杀Client再次调用transact()时会立即抛出DeadObjectException。这不是Bug而是Binder的正常保护机制。但问题在于很多App没做降级处理直接Crash。正确姿势

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑