前兩天測試提了一個crash的bug,崩潰棧以下:java
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 : android
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 moreapp
雖然說一眼就看出是binder通信引起的崩潰,但就如同OOM問題,定性問題容易,但定位問題卻不容易。函數
下面開始問題分析:oop
1) 分析拋出的緣由或條件,查看源碼android_util_binder.cpp,查看系統層在什麼狀況下會拋出TransactionTooLargeException,下圖中可知測試
在binder驅動處理失敗後,若是傳輸的parcel體積超過200kb,則會拋出TransactionTooLargeException,所以引起該問題的緣由是binder調用大數據
傳輸的數據太大致使,問題分析重點應側重binder數據傳輸。spa
2) 分析崩潰棧,找出引起該問題的binder調用是ActivityManagerProxy.activityStopped,從中大概推知問題的發生時機在Activity stopped的時候。設計
3) 網上百度相關的解決方案,關鍵詞是TransactionTooLargeException activityStopped,現象相似的問題、緣由、解決方案以下:3d
問題緣由: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用於大數據量傳輸,那就會出現問題。