2021年4月11日日曜日

FragmentはPublic以外でクラス定義すると落ちる

Fragmentをpublic以外のアクセス修飾子でクラス定義していると、FragmentManagerに追加する際、以下のIllegalStateExceptionが投げられクラッシュする。

Caused by: java.lang.IllegalStateException: Fragment com.sample.test.myapplication.MainActivity.MyDialogFragment must be a public static class to be  properly recreated from instance state.
   at androidx.fragment.app.FragmentTransaction.doAddOp(FragmentTransaction.java:165)
   at androidx.fragment.app.BackStackRecord.doAddOp(BackStackRecord.java:179)
   at androidx.fragment.app.FragmentTransaction.add(FragmentTransaction.java:125)
   at androidx.fragment.app.DialogFragment.show(DialogFragment.java:154)
   at com.sample.test.myapplication.MainActivity.onClick(MainActivity.java:32)
このエラーが出たときの対処法は、
Fragmentを別のファイルに定義し、publicクラスにすればよい。
本記事では、何故このような制約があるのかについて書いていく。


何故エラーは起こるのか

Androidのシステムは、スマホを横にして横表示に切り替えると、画面を横の比率に調整しつつ画面の内容を維持する仕組みがある。



ボタン押下でDialogFragmentを表示させ、
そのまま横にするとダイアログが維持されている図

このときアプリ内部では、縦画面のActivityを破棄し、横画面用のActivityへの作り直しが発生している。Activityが作り直されると当然Fragmentも破棄、再生成が行われる。

しかし、このときFragmentのコンストラクタがPublicではなかった場合、AndroidシステムはFragmentのインスタンスの生成ができず、この画面を復旧させる機能が実現できなくなる。

なので、
Fragmentの生成は、外部からでもできるようにpublicにしないといけない。
というルールが備わった。


実装ではどうなっているのか

このpublicのチェックはFragmentManagerに追加時に行われる。FragmentManagerに追加しない限りFragmentが再生成する必要がないわけなので、当然のタイミングだと思う。

実装内容はここ

/** FragmentTransaction.java */

void doAddOp(int containerViewId, Fragment fragment, @Nullable String tag, int opcmd) {
    final Class<?> fragmentClass = fragment.getClass();
    final int modifiers = fragmentClass.getModifiers();
    if (fragmentClass.isAnonymousClass() || !Modifier.isPublic(modifiers)
            || (fragmentClass.isMemberClass() && !Modifier.isStatic(modifiers))) {
        throw new IllegalStateException("Fragment " + fragmentClass.getCanonicalName()
                + " must be a public static class to be  properly recreated from"
                + " instance state.");
    }
表示させるフラグメントのClassクラスを取得して以下をチェックしている
  • 匿名クラスか?
  • ⇒ 当然、匿名クラスなら外部からコンストラクタが実行できない
  • Publicなクラス以外か?
  • ⇒ Publicクラス以外は外部からコンストラクタが実行できない
  • インナークラスの場合、Staticなクラスではないか?
  • ⇒ たとえPublicクラスであっても、インナークラスの場合はstaticでないと、外部からコンストラクタは呼べない
このチェックに引っかかれば、IllegalStateExceptionが投げられる仕組みだ。

以上の理由から、
Fragmentはpublicクラスにすることで、このエラーを回避できる。

0 件のコメント:

コメントを投稿