TransactionTooLargeException原因分析

前两天测试提了一个crash的bug,崩溃栈如下:

Device Manufacturer : Meizu
Device Model        : M5 Note
Android Version     : 7.0
Android SDK         : 24
App VersionName     : 1.1.2
App VersionCode     : 16
App Max Mem         : 512MB
UUID                : 864105030589222
Timestamp           : 10-16 14:27:22.880
CurrentThread       : main#1
TotalMem\AvailMem   : 3796MB\1037MB
Crash Detail        : 

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) 分析抛出的原因或条件,查看源码android_util_binder.cpp,查看系统层在什么情况下会抛出TransactionTooLargeException,下图中可知

   在binder驱动处理失败后,如果传输的parcel体积超过200kb,则会抛出TransactionTooLargeException,因此引发该问题的原因是binder调用

   传输的数据太大导致,问题分析重点应侧重binder数据传输。

 

 2) 分析崩溃栈,找出引发该问题的binder调用是ActivityManagerProxy.activityStopped,从中大概推知问题的发生时机在Activity stopped的时候。

 3) 网上百度相关的解决方案,关键词是TransactionTooLargeException activityStopped,现象类似的问题、原因、解决方案如下:

  • 问题原因:FragmentStatePagerAdapter的实现有缺陷,因为其默认实现会持续保存历史Fragment实例的状态数据历史,在逐渐地积累、保存数据后,最终导致发送的数据包体积超过限制200KB 。

       

 

      参见:https://stackoverflow.com/questions/11451393/what-to-do-on-transactiontoolargeexception

 

  • 解决方案:重写FragmentStatePagerAdapter的saveState方法,使其不保存历史Fragment实例的状态数据。

       

 

 4) 工程中崩溃点也恰巧使用了FragmentStatePagerAdapter,现象也类似,然后立即应用该方案,但没有效果。

 5)继续分析该问题,深入代码进行白盒分析,根据日志输出和代码结构,最后定位问题原因构建Fragment实例时传递ArrayList给Fragment的构造函数,

     在Fragment的构造函数内部将该ArrayList作为Parcelable存放在Bundle,以供该Fragment初始化时从bundle中读取, 在数据量比较大时,就会抛出TransactionTooLargeException

 6) 解决方案:构建Fragment实例时传递ArrayList给Fragment的构造函数,在Fragment被加载时无需从Bundle中读取,这样可避免TransactionTooLargeException,又提高程序执行效率。

 7) 总结

     TransactionTooLargeException经常出现在binder通信的场景中,导致其出现的直接原因是binder通信的数据包过大,而根本原因是使用者的理解和设计软件问题,因为binder通信的设计初衷是

跨进程的小规模数据体量的通信,从其内存设置就可看出:binder空间最大是1MB,而且是被所有进程共享。如果不理解binder的设计和适用场景,错误地将binder用于大数据量传输,那就会出现问题。

 

posted @ 2018-10-23 09:40  tgltt  阅读(4772)  评论(0编辑  收藏  举报