TransactionTooLargeException
TransactionTooLargeException 不是单一“字节数超过阈值”的结果;驱动分配、并发占用、对象/FD 成本、native status 和 Java 映射共同影响最终表现。下面的判别图把源码路径分层。
本文承接 binder_alloc_buf分配、Binder大数据传输 和 Binder同步调用。TransactionTooLargeException 是 Java 层对 FAILED_TRANSACTION 的启发式命名,不是内核唯一错误类型,也没有可靠信息告诉你请求还是 reply 太大。本文把 allocator errno、Native status、JNI 阈值和 Java 诊断日志分开追踪。
1. 内核分配
源码文件:kernel/common/drivers/android/binder.c
事务目标 buffer 由 binder_alloc_new_buf 分配。失败时,驱动根据 errno 记录:-ESRCH 目标 vma 已清理,-ENOSPC 空间不足,-ENOMEM 内核分配失败,并将同步事务转为 BR_FAILED_REPLY。-ENOSPC 可能是连续空间碎片、async 配额或并发事务占用,而不等于单个 Parcel 超过固定常数。
2. Native状态
源码文件:frameworks/native/libs/binder/IPCThreadState.cpp
waitForResponse 收到 BR_FAILED_REPLY 返回 FAILED_TRANSACTION;logExtendedError 在驱动支持 extended error 时读取额外 errno,ENOSPC 会打印“Binder buffer full. Too many or too large transactions.”。这条日志比 Java 异常名更接近实际原因。
3. JNI启发式
源码文件:frameworks/base/core/jni/android_util_Binder.cpp
case FAILED_TRANSACTION: {
ALOGE("!!! FAILED BINDER TRANSACTION !!! (parcel size = %d)", parcelSize);
if (canThrowRemoteException && parcelSize > 200*1024) {
exceptionToThrow = "android/os/TransactionTooLargeException";
} else {
exceptionToThrow = canThrowRemoteException
? "android/os/DeadObjectException" : "java/lang/RuntimeException";
}
}JNI 以 200 KiB 作为“看起来像大 payload”的启发式阈值。小 Parcel 的 FAILED_TRANSACTION 可能被包装为 DeadObjectException,因为驱动还可能因 malformed transaction、已关闭 fd 或远端死亡返回相同状态。异常类型不能反推出唯一内核原因。
4. Java说明
源码文件:frameworks/base/core/java/android/os/TransactionTooLargeException.java
Java 文档说明 Binder transaction buffer 当前固定约 1 MiB,且由进程中所有并发事务共享;因此多个中等事务同时在途也可能失败。异常可能发生在发送参数或返回结果,调用方无法可靠区分两者,应按部分失败处理。
5. 800KiB预警
源码文件:frameworks/base/core/java/android/os/Binder.java
framework 在 checkParcel 中对 dataSize >= 800*1024 的 Parcel 打印 wtfStack 预警。这不是硬拒绝阈值,真正拒绝仍由驱动 allocator、对象 fixup 和并发空间决定;预警用于在接近上限时提前暴露调用位置。
6. 接近与超过
源码文件:frameworks/native/libs/binder/tests/binderLibTest.cpp
测试使用约 950,000 字节的 vector 验证接近上限仍可成功,再用约 1,050,000 字节 vector 断言 FAILED_TRANSACTION。这证明固定 1 MiB 是重要边界,但不证明所有设备/并发状态都在同一精确字节处失败。
7. 并发占用
即使单个请求小于 1 MiB,目标进程已有多个同步事务、reply 尚未释放,或 oneway buffer 消耗 async 空间,也可能让新的 allocation 找不到连续空间。排查需要读取 binder allocator 的 allocated/free/largest free、free async space 和 outstanding transaction,而不是只打印当前 Parcel dataSize。
8. Blob与fd分流
大块内容若使用 Parcel::writeBlob 且 fd 允许,超过 16 KiB 的 payload 转为 Ashmem fd,不把内容全部放进 Binder transaction。若 mAllowFds 为 false,仍会尝试内联,可能触发同样的失败。把大数组换成 Blob 只有在接收端正确读取 fd/mmap 时才有效。
9. 对象与结构成本
Transaction size 不只包括业务 byte array,还包括 Parcelable padding、Binder object offsets、fd、security context 和 extra buffers。大量小 Binder/fd 对象可能在 dataSize 不大时耗尽对象/引用处理资源;malformed offsets 也会产生 BR_FAILED_REPLY 而非真正的超大数据。
10. 诊断顺序
- 记录发送 Parcel 与 reply 的
dataSize、对象数量和 fd 数量。 - 检查 Java
checkParcel/JNIparcel size日志。 - 读取 Native extended error,区分
ENOSPC、ESRCH、ENOMEM。 - 检查目标 proc allocator 的最大连续 free block、async space 和在途事务。
- 判断失败发生在 request 还是 reply;若无法判断,按部分失败设计重试/恢复。
- 对大内容改用 Blob、Ashmem/memfd、MemoryHeapBase 或分片协议,并验证生命周期。
11. 恢复策略
减少单次 Parcel、分页查询、避免 Bundle 携带大 bitmap、释放不必要的并发 reply,是首选修复。调大线程池不能扩大 Binder buffer;增加线程反而可能提高并发占用。对于长期共享数据,传 fd/offset/size 并建立明确 ownership,比重复发送字节数组更可靠。
12. 阅读检查
给定三个失败:1.05 MiB 单请求、200 KiB 请求在大量事务并发时失败、4 KiB malformed fd transaction,分别沿 allocator、并发空间、JNI 启发式和对象 fixup 指出可能状态。再说明为何三者都可能最终显示为 FAILED_TRANSACTION,但恢复策略不同。
