Intent数据大小限制及TransactionTooLargeException异常
一. 异常表现:
崩溃日志:
java.lang.RuntimeException: android.os.TransactionTooLargeException: data parcel size 531500 bytes
at android.app.ActivityThread$StopInfo.run(ActivityThread.java:4112)
at android.os.Handler.handleCallback(Handler.java:836)
at android.os.Handler.dispatchMessage(Handler.java:103)
at android.os.Looper.loop(Looper.java:203)
at android.app.ActivityThread.main(ActivityThread.java:6519)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1113)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:974)
Caused by: android.os.TransactionTooLargeException: data parcel size 531500 bytes
at android.os.BinderProxy.transactNative(Native Method)
at android.os.BinderProxy.transact(Binder.java:627)
at android.app.ActivityManagerProxy.activityStopped(ActivityManagerNative.java:3990)
at android.app.ActivityThread$StopInfo.run(ActivityThread.java:4104)
... 7 more
虽说一眼就看出是binder通讯引发的崩溃,但就如同OOM问题,定性问题容易,但定位问题却不容易。
二. 为什么会出现异常?
官方说明:
1.TransactionTooLargeException
简单来说:一个应用进程的所有 AIDL 调用都是共用一个 Binder transaction buffer,而这个 buffer 的大小仅仅只是 1Mb,当所有的远程调用的参数或者这些调用返回值的大小加起来超过 1Mb 的话就会抛出 TransactionTooLargeException 异常。
Binder 传输缓冲区是一个限制的大小的区域,大小为 1MB,这块缓冲区用于所有进程间的通信,也就是 Binder 通信。这些传输包括 onSaveInstanceState , startActivity 和其他与系统的交互,当传输的数据超过这个大小的时候就会抛出异常。
特别是 onSaveInstanceState 方法,因其需要在 Activity 返回的时候提供数据,官网建议是数据大小不大于 50K.
注意:
1.是不是只要通过 Bundle 传递数据,就会面临序列化的问题?
并不是,Activity 之间传递数据,首先要考虑跨进程的问题,而 Android 中又是通过 Binder 机制来解决跨进程通信的问题。涉及到跨进程,对于复杂数据就要涉及到序列化和反序列化的过程,这就注定是一次值传递(深拷贝)的过程。
Fragment 本身是不涉及跨进程的,这里虽然使用了 Bundle 传输数据,但是并没有通过 Binder,也就是不存在序列化和反序列化。和 Fragment 数据传递相关的 Bundle,其实传递的是原对象的引用。
结论:通过Fragment 的setArguments(Bundle) 传递一个 Bundle ,这里虽然使用了 Bundle 传输数据,但是并没有通过 Binder,也就是不存在序列化和反序列化。和 Fragment 数据传递相关的 Bundle,其实传递的是原对象的引用。(做个试验,弹出 DialogFragment 时传递一个对象,Dialog 中修改数据后,在 Activity 中该对象被修改了)
2.Activity onSaveInstanceState 中保存的 Bundle 信息是存在内存中的,且因为是涉及到 Activity 的状态的保存,就需要交由 ActivityManager 进程去做一个管理,所以就需要 Binder 传输做一个跨进程的通信将 Bundle 的数据传递给 ActivityManager。因此 onSaveInstanceState 也涉及到了 Binder 传输,自然而然就受到 Binder 缓冲区大小的限制.
3.FragmentStatePagerAdapter的实现有缺陷,因为其默认实现会持续保存历史Fragment实例的状态数据历史,在逐渐地积累、保存数据后,最终导致发送的数据包体积超过限制50KB
三. 解决及规避
- 不通过intent传大数据,使用EventBus
使用EventBus postSticky()方法发送,它会将事件缓存到 stickyEvents 这个 Map 对象中,以待下次注册时,将这个事件取出,抛给注册的组件。
接收:
@Subscribe(threadMode = ThreadMode.MAIN,sticky = true)
public void onStickyEvent(MyStickyEvent event){
//...
}
- 重写FragmentStatePagerAdapter的saveState方法 ,使其不保存历史Fragment实例的状态数据.
public class TestAdapter extends FragmentStatePagerAdapter {
@Override
public Parcelable saveState() {//空实现,不传数据
return null;
}
}
参考链接:
面试常客:Intent 能传递多大 Size 的数据?| 付阿里的建议方案!
网友评论