Androidアプリで画像ビューア画面を表示するとTransactionTooLargeException
が発生してクラッシュする問題に頭を悩ませていた。
発生している状況について
MainActivity (ホームFragment) → DetailActivity (詳細Fragment → リストFragment ) → ImageActivity (画像Fragment)に遷移してから、しばらく経つとTransactionTooLargeExceptionが発生する。
E/JavaBinder: !!! FAILED BINDER TRANSACTION !!! (parcel size = 1187868) E/AndroidRuntime: FATAL EXCEPTION: main Process: MY_APPLICATION, PID: 8951 java.lang.RuntimeException: android.os.TransactionTooLargeException: data parcel size 1187868 bytes at android.app.servertransaction.PendingTransactionActions$StopInfo.run(PendingTransactionActions.java:160) at android.os.Handler.handleCallback(Handler.java:873) at android.os.Handler.dispatchMessage(Handler.java:99) at android.os.Looper.loop(Looper.java:216) at android.app.ActivityThread.main(ActivityThread.java:7285) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:494) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:975) Caused by: android.os.TransactionTooLargeException: data parcel size 1187868 bytes at android.os.BinderProxy.transactNative(Native Method) at android.os.BinderProxy.transact(Binder.java:1145) at android.app.IActivityManager$Stub$Proxy.activityStopped(IActivityManager.java:3875) at android.app.servertransaction.PendingTransactionActions$StopInfo.run(PendingTransactionActions.java:144) at android.os.Handler.handleCallback(Handler.java:873) at android.os.Handler.dispatchMessage(Handler.java:99) at android.os.Looper.loop(Looper.java:216) at android.app.ActivityThread.main(ActivityThread.java:7285) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:494) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:975)
この例外はBundleが1MBを超えると発生する。ここでは 1.19MBになっているのでクラッシュしているのは、理解できるがそもそもの原因がわからない。
画像ビューア画面の表示用にBitmapをBundlerに詰めてクラッシュしているのであればわかりやすいんだけど、画像のURLをStringオブジェクトで渡しているだけである……
TransactionTooLargeExceptionが発生する原因
Android 7.0の変更点に TransactionTooLargeExceptions について掲載されています。
多くのプラットフォーム API は、Binder トランザクションで送信される大きなペイロードをチェックし、暗黙的にログ記録したり、削除したりするのではなく TransactionTooLargeExceptions を RuntimeExceptions として再度スローするようになりました。一般的な例としては、Activity.onSaveInstanceState() で大量のデータを格納することです。これにより、アプリが Android 7.0 をターゲットにしている場合は、ActivityThread.StopInfo で RuntimeException がスローされます。
Android 7.0 の動作の変更点
前述の通り、画像ビューア画面を表示する際に BundleにBitmapを詰めてはいない ので、よくあるケースからは外れているようです。Bitmapを詰めているのであれば本記事を書いたりすることもなく修正して終わっていたはず……。
Bitmap利用以外でのTransactionTooLargeException発生例
Bitmapを使ってパンクしている以外の例は下記の通り。
- SoundPoolを開放したら治った例
- FragmentStatePagerAdapterを用いたViewPager実装の場合
- https://stackoverflow.com/questions/11451393/what-to-do-on-transactiontoolargeexception/43193425#43193425
- AndroidでFatal Exception: java.lang.RuntimeException android.os.TransactionTooLargeException: data parcel size N bytes │ 技術ログ
- https://stackoverflow.com/questions/39098590/android-os-transactiontoolargeexception-on-nougat
解決した
「android - What to do on TransactionTooLargeException - Stack Overflow」 によると、FragmentStatePagerAdapter
のサブクラスで Fragmentごとのstates を保持し続けていているようです。
ホームFragment と 詳細Fragment の両画面でFragmentStatePagerAdapterを利用していました。塵も積もれば山となるでトランザクションペイロードが大きくなってしまい DetailActivityからImageActivityへ遷移するタイミングで DetailActivity側の保存処理が走り Bundleが1MBを超えたのがわかりました。
画像ビューア側でBitmapを扱っているので、画像ビューア画面が悪さをしているのでは?と推測してデバッグをしていましたが、結果的には画像ビューア画面はまったく関係なく、そこまでの遷移元の画面が悪いという結論となりました。
完全解決ではなかった……
toolargetool を使って、さらに解析したところ他にもガツンと大きなデータを使っている画面を発見した。