バックスタック管理:リスターターは、所有者の初期化段階でのみ作成する必要があります

22
Gabi 2019-06-11 22:13.

MainActivityの下部のナビゲーションバーを使用して、いくつかのフラグメントを処理しています。これは、それらを切り替えるために使用されるコードです。

private val mOnNavigationItemSelectedListener = BottomNavigationView.OnNavigationItemSelectedListener { item ->
    if (item.isChecked &&
        supportFragmentManager.findFragmentById(R.id.act_main_fragment_container) != null
    )
        [email protected] false
    val fragment =
        when (item.itemId) {
            R.id.navigation_home      -> fragments[0]
            R.id.navigation_bookings  -> fragments[1]
            R.id.navigation_messages  -> fragments[2]
            R.id.navigation_dashboard -> fragments[3]
            R.id.navigation_profile   -> fragments[4]
            else                      -> fragments[0]
        }
    this replaceWithNoBackStack fragment
    [email protected] true
}

メソッドreplaceWithNoBackstackは、これの単なる省略形です。

supportFragmentManager
    ?.beginTransaction()
    ?.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
    ?.replace(containerId, fragment)
    ?.commit()

問題は、それらをより速く切り替えると、次の例外を除いてアプリがクラッシュすることです。

java.lang.IllegalStateException:リスタータは、androidx.savedstate.SavedStateRegistryController.performRestore(SavedStateRegistryController.java:59)at androidx.fragment.app.Fragment.performCreate(Fragment.java:2580)atandroidxでの所有者の初期化段階でのみ作成する必要があります。 androidx.fragment.app.FragmentManagerImpl.moveFragmentToExpectedState(FragmentManagerImpl.java:1237)at androidx.fragment.app.FragmentManagerImpl.moveToState(FragmentManagerImpl。 .fragment.app.BackStackRecord.executeOps(BackStackRecord.java:439)at androidx.fragment.app.FragmentManagerImpl.executeOps(FragmentManagerImpl.java:2075)at androidx.fragment.app.FragmentManagerImpl.executeOpsTogether(FragmentManagerImpl.java:1865) androidx.fragment.app.FragmentManagerImpl.execPendingActions(FragmentMana)のandroidx.fragment.app.FragmentManagerImpl.removeRedundantOperationsAndExecute(FragmentManagerImpl.java:1820)gerImpl.java:1726)androidx.fragment.app.FragmentManagerImplで$2.run(FragmentManagerImpl.java:150) at android.os.Handler.handleCallback(Handler.java:789) at android.os.Handler.dispatchMessage(Handler.java:98) at android.os.Looper.loop(Looper.java:164) at android.app.ActivityThread.main(ActivityThread.java:6709) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:240)at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:769)たくさん検索していて、答えが見つかりませんでした。

API呼び出しを実行し、アプリをバックグラウンドに置き、応答を待つと、このエラーも発生しました。アプリに戻ったときに、ダイアログフラグメントをすぐに表示しようとしているため、アプリがクラッシュします(理由これは、ダイアログフラグメントを表示する時点で、バックグラウンドから戻ったときにフラグメントを再作成するトランザクションがまだ進行中であるためだと思います)。他の解決策を見つけることができなかったので、ダイアログに500msの遅延を設定することで、これをハッキーな方法で解決しました。

これに関する詳細が必要かどうかお問い合わせください。前もって感謝します!

考えられる一時的な解決策

編集 私はアプリの互換性の依存性をにダウングレードすることでこの問題を解決しましたandroidx.appcompat:appcompat:1.0.2が、将来更新する必要があるため、これは一時的な解決策にすぎません。誰かがそれを理解してくれることを願っています。

編集2 フラグメントトランザクションからsetTransition()を削除することで問題を解決しました。少なくとも私はAndroidアプリが一般的に良いトランジションを持っていない理由を知っています

編集3 この問題を回避し、物事をスムーズに機能させるための最善の解決策は、ViewPagerを使用してボトムバーのナビゲーションを処理することです。

11 answers

3
진홍빛 2019-06-12 16:47.

'androidx.core:core-ktx:1.0.2'を使用している場合は、1.0.1に変更してみてください

ライフサイクル(またはrxFragment)とandroidx_appcompat:alpha05を使用している場合は、バージョンを変更してみてください。
例)appcompat:1.1.0-beta01または1.0.2

ターゲットフラグメントを再利用したときに状態を保存するとエラーとして表示されると思います(onPause-onResume)。

8
Drown Coder 2019-06-27 16:38.

バージョン1.0.0は状態をチェックしていないため、例外はスローされませんが、バージョン1.1.0はソースコードを変更するため、例外がスローされます。

これはフラグメントバージョン-1.1.0のソースコードであり、メソッドを呼び出しますperformRestore

    void performCreate(Bundle savedInstanceState) {
        if (mChildFragmentManager != null) {
            mChildFragmentManager.noteStateNotSaved();
        }
        mState = CREATED;
        mCalled = false;
        mSavedStateRegistryController.performRestore(savedInstanceState);
        onCreate(savedInstanceState);
        mIsCreated = true;
        if (!mCalled) {
            throw new SuperNotCalledException("Fragment " + this
                    + " did not call through to super.onCreate()");
        }
        mLifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE);
    }

/**
the exception
**/
public void performRestore(@Nullable Bundle savedState) {
        Lifecycle lifecycle = mOwner.getLifecycle();
        if (lifecycle.getCurrentState() != Lifecycle.State.INITIALIZED) {
            throw new IllegalStateException("Restarter must be created only during "
                    + "owner's initialization stage");
        }
        lifecycle.addObserver(new Recreator(mOwner));
        mRegistry.performRestore(lifecycle, savedState);
    }

これはバージョン1.0.0のソースコードです。呼び出さなかったperformRestoreため、例外はスローされません。

void performCreate(Bundle savedInstanceState) {
    if (mChildFragmentManager != null) {
        mChildFragmentManager.noteStateNotSaved();
    }
    mState = CREATED;
    mCalled = false;
    onCreate(savedInstanceState);
    mIsCreated = true;
    if (!mCalled) {
        throw new SuperNotCalledException("Fragment " + this
                + " did not call through to super.onCreate()");
    }
    mLifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE);
}

ある二つの異なる解決策これを扱うことができます:
最初のソリューションは、トランザクションを分割することです。
私たちはいつも使用しているためreplaceか、マージremoveし、add1つのトランザクションに。トランザクションを次のように2つのトランザクションに分割できます。

FragmentTransaction ft = manager.beginTransaction();
        Fragment prev = manager.findFragmentByTag(tag);
        if (prev != null) {
        //commit immediately
            ft.remove(prev).commitAllowingStateLoss();
        }
        FragmentTransaction addTransaction = manager.beginTransaction();
        addTransaction.addToBackStack(null);
        addTransaction.add(layoutId, fragment,
                tag).commitAllowingStateLoss();

この2つのトランザクションは、ハンドラーによって処理される2つの異なるメッセージになるためです。
2つ目の解決策は、事前に状態を確認することです。ソースコードをたどることができます、事前に状態を確認してください

FragmentTransaction ft = manager.beginTransaction();
        Fragment prev = manager.findFragmentByTag(tag);
        if (prev != null) {
        if (prev.getLifecycle().getCurrentState() != Lifecycle.State.INITIALIZED) {
            return;
        }
            ft.remove(prev);
        }

最初の方法をお勧めします。2番目の方法はソースコードをフォローすることなので、ソースコードがコードを変更すると無効になります。

4
Mako Storm 2019-09-05 04:20.

私も同じ問題を抱えていました。

val fragment = Account.activityAfterLogin
        val ft = activity?.getSupportFragmentManager()?.beginTransaction()
        //error
        ft?.setCustomAnimations(android.R.anim.slide_in_left,android.R.anim.slide_out_right)0
        ft?.replace(R.id.framelayout_account,fragment)
        ft?.commit()

ライブラリのバージョンを変更しても役に立ちませんでした。メソッドのft?.AddToBackStack(null)後に行を追加することでこれを解決しましft?.setCustomAnimations ()た。それだけです。アニメーションは機能し、クラッシュはありません。

2
Vlad Yatsenko 2019-06-22 04:53.

androidx.appcompat:appcompat:1.0.2の実装をapiに変更しましたが、機能しました

2
Jonathan Kely Kosi 2019-10-30 06:39.

それが役立つ場合は、BottomNavigationViewとsetCustomAnimationsで同じ問題が発生しました。基本的に、フラグメントをすばやく切り替えると、前のフラグメントが終了していないときにFragmentTransactionが開始され、クラッシュする可能性があります。

これを回避するには、移行が完了するまでナビゲーションバーを無効にします。そこで、BottomNavigationViewアイテムを有効/無効にするメソッドを作成し(BottomNavigationView自体を無効にしてもメニューが無効にならないか、方法が見つかりませんでした)、移行が完了したら再度有効にします。

アイテムを無効にするには、FragmentTransitionを開始する直前に次のメソッドを呼び出します。

public void toggleNavigationBarItems(boolean enabled) {
   Menu navMenu = navigationView.getMenu();
   for (int i = 0; i < navMenu.size(); ++i) {
       navMenu.getItem(i).setEnabled(enabled);
   }
}

それらを再度有効にするために、BottomNavigationViewからロードされたフラグメントの抽象Fragmentクラスを作成しました。このクラスでは、onCreateAnimatorをオーバーライドし(View Animationを使用する場合は、onCreateAnimationをオーバーライドする必要があります)、onAnimationEndを再度有効にします。

@Nullable
@Override
public Animator onCreateAnimator(int transit, boolean enter, int nextAnim) {
    if(enter){ // check the note below
        Animator animator = AnimatorInflater.loadAnimator(getContext(), nextAnim);
        animator.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                myActivity.toggleNavigationBarItems(true)
            }
        });
        return animator;
    }
    return super.onCreateAnimator(transit, enter, nextAnim);
}

注:入力アニメーションと終了アニメーションの長さは同じであるため、入力アニメーションは終了アニメーションの後に開始されるため、同期する必要はありません。そのため、if(enter)で十分です。

2
Mahdi Zareei 2020-05-14 23:12.

addfragmentメソッドにadd'synchronized 'でこの問題を修正しました

public void addFragment(int contentFrameId, Fragment fragment, Bundle param, boolean addToStack) {
    try {
        if (!fragment.isAdded()) {
            if (param != null) {
                fragment.setArguments(param);
            }

            FragmentManager fragmentManager = getSupportFragmentManager();
            FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction()
                    .add(contentFrameId, fragment)
                    .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);

            if (addToStack)
                fragmentTransaction.addToBackStack(fragment.getClass().toString());

            fragmentTransaction.commit();
        }
    } catch (IllegalStateException e) {
        handleError(e.getMessage());
    } catch (Exception e) {
        handleError(e.getMessage());
    }
}

public synchronized void addFragment(int contentFrameId, Fragment fragment, Bundle param, boolean addToStack) {
    try {
        if (!fragment.isAdded()) {
            if (param != null) {
                fragment.setArguments(param);
            }

            FragmentManager fragmentManager = getSupportFragmentManager();
            FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction()
                    .add(contentFrameId, fragment)
                    .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);

            if (addToStack)
                fragmentTransaction.addToBackStack(fragment.getClass().toString());

            fragmentTransaction.commit();
        }
    } catch (IllegalStateException e) {
        handleError(e.getMessage());
    } catch (Exception e) {
        handleError(e.getMessage());
    }
}
1
Geoffrey Powell 2019-07-26 07:19.

このバグは、androidx.appcompat:appcomat:1.1.0-rc01およびandroidx.fragment:fragment:1.1.0-rc03を使用して解決されているようです。

https://developer.android.com/jetpack/androidx/releases/fragment#1.1.0-rc03

1
Hadi Ahmadi 2019-09-30 09:03.

を使用すると、この問題が発生しsetCustomAnimationsます。削除することでsetCustomAnimations私の問題は解決しました。また、setCustomAnimationを使用しても、表示する前にフラグメントの新しいインスタンスを作成しても問題はありません。

編集:別の方法は、バックスタックにフラグメントを追加することです。

1
simekadam 2019-11-26 04:34.

すべてのボトムナビゲーションフラグメントトランザクションのcommitNow()代わりにcommit()を使用することで、これを修正することができました(うまくいけば😃)。フラグメント間でカスタムトランジションを引き続き使用できるため、このアプローチの方が好きです。

注:これは、最下位のナビゲーショントランザクションをバックスタックに追加したくない場合にのみ解決策です(とにかく行うべきではありません)。

1
Dmitriy Pavlukhin 2019-12-05 08:13.

以外は何も機能しませんでした https://stackoverflow.com/a/56783167/7331042解決策ですが、バックスタックにトランザクションを追加するため、まだ完全ではありませんでした。したがって、下部のナビゲーションですべてのボタンを押すと、バックスタックにすべてのフラグメントが少なくとも1つあります。私はこのソリューションを少し改善したので、.replace()アクションアニメーションでアプリをクラッシュさせることはありません。

コードは次のとおりです。

if (getChildFragmentManager().getBackStackEntryCount() > 0) {
    getChildFragmentManager().popBackStack();
}

FragmentTransaction addTransaction = getChildFragmentManager().beginTransaction();
addTransaction.setCustomAnimations(R.animator.fragment_fade_in, R.animator.fragment_fade_out);
addTransaction.addToBackStack(null);
addTransaction.add(R.id.frame, fragment, fragment.getClass().getName()).commitAllowingStateLoss();
0
Kumar Purushottam 2020-12-04 00:13.

このケースを作成する別の方法を見つけました。

ケース-1

  1. アクティビティでフレームレイアウトのフラグメントを膨らませる
  2. APIリクエストを開始します(アプリがフォアグラウンドにあるときにAPIレスポンスを消費しないでください)
  3. アプリをバックグラウンドに保つ
  4. APIリクエストを消費します(APIレスポンスに別のフラグメントを追加したいとします)
  5. 同じフレームレイアウトで.replace()メソッドを使用して別のフラグメントを膨らませます
  6. クラッシュを作成できるようになります

ケース2

  1. アクティビティでフレームレイアウトのフラグメントを膨らませる
  2. APIリクエストを開始します
  3. フォアグラウンドでAPIを使用します(fragment-managerの.replace()メソッドを使用して、API応答に別のフラグメントを追加するとします)
  4. アプリをバックグラウンドに置く
  5. アプリケーションを再作成します(これは、「アクティビティを保持しない」、権限の変更、システム言語の変更を使用して行うことができます)
  6. アプリケーションに戻ってください
  7. あなたの活動は再作成を開始します
  8. アクティビティは、すでに膨張しているフラグメントを自動的に再作成します(ポイント1)。
  9. ポイント8の後、再作成のケースでAPIが再度リクエストされていることを確認してください
  10. API応答を消費し、.replace()メソッドを使用して別のフラグメントを膨らませます
  11. クラッシュを作成できるようになります(この場合のように、すでにトランジションがポイント8で実行されており、ポイント10で別のフラグメントを追加しています)

Related questions

MORE COOL STUFF

ケイト・ブランシェットは3日間一緒に夫と一緒に寝て、25年経ってもまだ夫と結婚しています

ケイト・ブランシェットは3日間一緒に夫と一緒に寝て、25年経ってもまだ夫と結婚しています

ケイト・ブランシェットは、夫に会ったとき、典型的な交際のアドバイスに逆らいました。

マイケルシーンが非営利の俳優である理由

マイケルシーンが非営利の俳優である理由

マイケルシーンは非営利の俳優ですが、それは正確にはどういう意味ですか?

ホールマークスターのコリンエッグレスフィールドがRomaDramaLiveでスリル満点のファンと出会う![エクスクルーシブ]

ホールマークスターのコリンエッグレスフィールドがRomaDramaLiveでスリル満点のファンと出会う![エクスクルーシブ]

特徴的なスターのコリン・エッグレスフィールドは、RomaDrama Liveでのスリル満点のファンとの出会いについて料理しました!加えて、大会での彼のINSPIREプログラム。

「たどりつけば」をオンラインでストリーミングできない理由

「たどりつけば」をオンラインでストリーミングできない理由

ノーザンエクスポージャーが90年代の最も人気のある番組の1つになった理由を確認するには、Blu-rayまたはDVDプレーヤーをほこりで払う必要があります。

バイオニック読書はあなたをより速く読むことができますか?

バイオニック読書はあなたをより速く読むことができますか?

BionicReadingアプリの人気が爆発的に高まっています。しかし、それは本当にあなたを速読術にすることができますか?

ドミニカのボイリング湖:アクセスは簡単ではありませんが、ハイキングする価値があります

ドミニカのボイリング湖:アクセスは簡単ではありませんが、ハイキングする価値があります

ドミニカのボイリング湖は、世界で2番目に大きいボイリング湖です。そこにたどり着くまでのトレッキングは大変で長いですが、努力する価値は十分にあります。

私たちの水をきれいに保つのを助けるためにあなたの髪を寄付してください

私たちの水をきれいに保つのを助けるためにあなたの髪を寄付してください

サロンからのヘアトリミングや個人的な寄付は、油流出を吸収して環境を保護するのに役立つマットとして再利用できます。

ホワイトハウスの最も記憶に残る結婚式を見てください

ホワイトハウスの最も記憶に残る結婚式を見てください

過去200年以上の間にホワイトハウスで結婚したのはほんの数人です。彼らは誰でしたか、そしてそこで結婚式を獲得するために何が必要ですか?

ライアン・フェルトンがジャロプニックを去る。アメリカの詐欺と嘘つきは安堵のため息をつく。スタッフが再び昼食時に肉を注文する

ライアン・フェルトンがジャロプニックを去る。アメリカの詐欺と嘘つきは安堵のため息をつく。スタッフが再び昼食時に肉を注文する

「あなたは知っているべきです」とライアン・フェルトンは電話にひそかに言いました、「私は本当にできないこと。

今日だけMovadoウォッチが50%オフで、手首にちょっとした贅沢を加えましょう

今日だけMovadoウォッチが50%オフで、手首にちょっとした贅沢を加えましょう

ノードストロームラックのMovadoイベントMovadoは、クラシックで象徴的な時計ブランドですが、価格が1,000ドルを超えるため、ほとんどの人にとって信じられないほど手に負えないと感じる可能性があります。しかし、ノードストロームラックのMovadoイベントを使用すると、小売価格の半分でゴージャスな時計を獲得できます。いくつかのスタイルは400ドル未満で提供されます。

L7は18年ぶりのショーをプレイしました

L7は18年ぶりのショーをプレイしました

これは素晴らしいことです。L7のオリジナルラインナップは18年ぶりに再会し、LAのエコーで完売したショーを演奏しました。

「またの名をグレイス」は、見られているという不穏な感覚を体現しています。

「またの名をグレイス」は、見られているという不穏な感覚を体現しています。

「またの名をグレイス」ジョーダン博士は、19世紀半ばのカナダの性別の役割と階級の違いが、グレイスの心を解き放とうとし続けています。

Zendaya Wishes Boyfriend Tom Holland Happy Birthday with Cuddly Photo: He 'Makes Me the Happiest'

Zendaya Wishes Boyfriend Tom Holland Happy Birthday with Cuddly Photo: He 'Makes Me the Happiest'

Zendaya shared a sweet photo in honor of boyfriend Tom Holland's 26th birthday Wednesday

小さな女性:脳卒中を患った後に病院から解放されたアトランタのジューシーな赤ちゃん:「まだ癒し」

小さな女性:脳卒中を患った後に病院から解放されたアトランタのジューシーな赤ちゃん:「まだ癒し」

シーレン「Ms.JuicyBaby」ピアソンは、先月脳卒中で入院した後、「もう一度たくさんのことをする方法を学ばなければならない」ため、言語療法を受けていることを明らかにしました。

エマストーンは彼女のクリフサイドマリブビーチハウスを420万ドルでリストアップしています—中を見てください!

エマストーンは彼女のクリフサイドマリブビーチハウスを420万ドルでリストアップしています—中を見てください!

オスカー受賞者の世紀半ばの家には、3つのベッドルーム、2つのバス、オーシャンフロントの景色があります。

ジーニー・メイ・ジェンキンスは、母乳育児の経験の中で、彼女は「本当に、本当に落ち込んでいる」と言います

ジーニー・メイ・ジェンキンスは、母乳育児の経験の中で、彼女は「本当に、本当に落ち込んでいる」と言います

ジーニー・メイ・ジェンキンスは、生後4か月の娘、モナコに母乳育児をしていると語った。

エッジ機能とは?!?

エッジ機能とは?!?

エッジ機能…それはセクシーに聞こえます、あなたのおばあちゃんはそれが何であるかを知らないでしょう、そしてあなたはおそらくまだどちらも知らないでしょう。それは大丈夫です、うまくいけば、私はより簡単な方法でエッジを理解するためのあなたの旅であなたを助けるためにここにいます。

また、より顧客重視を「試み」ていますか?

また、より顧客重視を「試み」ていますか?

顧客が好きなものから、顧客が好き/嫌いなものについて利害関係者の内臓が言うものに焦点が移る製品機能の議論を経験したことがありますか?チームの全員がお互いに何をすべきかを尋ねていて、誰もその製品に興味を持っていないように見える時期を経験したことがありますか?フィードバックループフィードバック—否定的な意味合いがありますよね?実際には、フィードバックは環境から私たちの行動への反応であり、私たちに利益をもたらすためにそれを使用するには、この反応を分析する必要があります。通常、これには従いません。

あなたがアメリカを怖がっているなら、あなたはまだ十分に怖がっていません

あなたがアメリカを怖がっているなら、あなたはまだ十分に怖がっていません

神学者とファシストは全体主義社会を望んでいます—そして彼らは世界中で1つの衝撃波を反響させようとしています。友人や人々でいっぱいの古いヨーロッパの古い街にある私の小さなカフェで、「彼らは本当にそれをしましたか?」「彼らはクレイジーですか?」「これからどうなるの?」"我が神よ!" 角を曲がったところにある公園で、犬たちは幸せな夏の輪の中で回転します。「信じられない!」「アメリカ人にできることはありますか?」"今、何が起きた?" 今、何が起きた?それがみんなの頭の中にある質問です。

私の技術の旅

速いお金があなたが望むものであるか、ストレスのない生活があなたが切望するものであるならば、私はあなたに技術があなたのものではないことを断固として言いたいです。私を信じて。

Language