酢ろぐ!

カレーが嫌いなスマートフォンアプリプログラマのブログ。

AndroidアプリがTransactionTooLargeExceptionでクラッシュする

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を使ってパンクしている以外の例は下記の通り。

解決した

android - What to do on TransactionTooLargeException - Stack Overflow」 によると、FragmentStatePagerAdapterのサブクラスで Fragmentごとのstates を保持し続けていているようです。

ホームFragment と 詳細Fragment の両画面でFragmentStatePagerAdapterを利用していました。塵も積もれば山となるでトランザクションペイロードが大きくなってしまい DetailActivityからImageActivityへ遷移するタイミングで DetailActivity側の保存処理が走り Bundleが1MBを超えたのがわかりました。

画像ビューア側でBitmapを扱っているので、画像ビューア画面が悪さをしているのでは?と推測してデバッグをしていましたが、結果的には画像ビューア画面はまったく関係なく、そこまでの遷移元の画面が悪いという結論となりました。

完全解決ではなかった……

toolargetool を使って、さらに解析したところ他にもガツンと大きなデータを使っている画面を発見した。