見出し画像

Deco Calendar Android版開発で困ったこと-FragmentStateAdapterとTransactionTooLargeException

Deco Calendar AppのAndroid版開発で厄介なことが起こりました。解決に数日かかりました...

このアプリは、スワイプ操作で月めくりがなされ、画面を何枚も用意する必要があります。その機能を実現するために、Android版ではViewPager2を用いています。そしてFragmentを何枚も用意する必要がありますので、ViewPager2のアダプターとしてFragmentStateAdapterを用いています。

困ったことに、このアプリでそれなりの容量の壁紙を設定すると、Activityを切り替えたときに落ちる現象が見られるようになりました。デバッグログを見ると、” TransactionTooLargeException”と表示されています。Androidでは、Activityが切り替わるときに、そのActivity(Fragment)のビットマップデータをBundlerに詰め込む作業が行われるのですが、1MBを超えると、上記例外が発生するようです。Fragmentにそれなりの容量のビットマップを貼り付けているのですから、起こって然るべきかな、と思います。

その回避方法は結構悩みました。Fragmentは単一のものではなく、Pagerですし。

回避方法は、暫定的ではあるのですが、一旦FragmentStateAdapterで生成されたFragmentをremoveして、再びActivityが復帰したときに再描画を行うことにしました。

親FragmentのonPause

    override fun onPause() {
       super.onPause()
       //adapterに紐付けるitemについて一旦空のものを設定する
       val dmmyItem = arrayListOf<Item>()
       val adapter = viewPager2.adapter as BaseFragmentPageAdapter
       adapter.updateList(dmmyItem) // DiffUtil.calculateDiff用のfunction
       adapter.removeFragments()
   }

FragmentStateAdapter内

    private var frags = arrayListOf<Fragment>()
    private var item = mutableListOf<Item>() //ビットマップ等の情報を格納したdataclass


//中略

    override fun createFragment(position: Int): Fragment {
      val fragment =  CalendarFragment.newInstance(getItem(position), holidays)
       frags.add(calendarFragment)
       return calendarFragment
    }
   
    fun removeFragments() {
       val iter = frags.iterator() //createFragmentでFragmentをArrayListに格納する
       while(iter.hasNext()) {
           val fragment = iter.next()
           fm.beginTransaction().remove(fragment).commit()
           iter.remove()
       }
   }

そして、親FragmentのonResumeが呼ばれたときに、Fragmentを再生成する作業をおこなっています。つまり、メモリに格納したデータを呼び出してそこで再描画(実際にはDiffutil.calculateをかけるfunctionであるupdateListを呼び出しているだけ)をおこなっています。

これでなんとかActivityが切り替わるときに例外が発生しなくなりました。パラパラマンガアプリやスライドショーなどのアプリを作る際にViewPager2は便利で必須ですが、落とし穴には注意しましょう。

この記事が気に入ったらサポートをしてみませんか?