長文失礼します。私は単純なアプリをいじっていて、方向の変更を越えてカスタム オブジェクトをフラグメントに保存したいと考えていました。以前はアクティビティ内で、これは onRetainNonConfigurationInstance() / getLastNonConfigurationInstance() メソッドを使用して処理されていました。これらのメソッドは非推奨になったため、ドキュメントではフラグメントと setRetainInstance(boolean) メソッドの使用を推奨しています。
私は先に進み、この方法をいじってみましたが、方向の変化を越えてフラグメントの状態を保存することになると、奇妙な動作の違いに気付きました. 最初に、私が遊んでいたアプリの非常に簡単な説明:
主な活動
フラグメント A (アプリの起動時に表示される最初のフラグメント)
これは、3 つの EditText コントロールを持つ単純なフラグメントです。レイアウト ファイルには、それぞれに ID があります。フラグメントには、選択するとフラグメント A をフラグメント B に置き換え、トランザクションをバックスタックに保存するボタンも含まれます。
フラグメント B
これは空のレイアウトのフラグメントです。バックがプッシュされると、フラグメント A がバックスタックから復元されます。
シナリオ
シナリオ A - setRetainInstance(false):
- アプリが起動し、フラグメント A が表示されます。
- EditText フィールドに値を入力し、ボタンを選択します。
- フラグメント B が表示されます。デバイスの向きを一度変えて、戻るキーを押します。
- フラグメント A は、入力された値 (ビュー ステート) がそのまま表示されます。
シナリオ A - setRetainInstance(true):
上記と同じ動作が行われます
シナリオ B - setRetainInstance(false):
- アプリが起動し、フラグメント A が表示されます。
- EditText フィールドに値を入力し、ボタンを選択します。
- フラグメント B が表示されます。デバイスの向きを 2 回変更して、戻るキーを押します。
- フラグメント A は、入力された値 (ビュー ステート) がそのまま表示されたままです。
シナリオ B - setRetainInstance(true):
- アプリが起動し、フラグメント A が表示されます。
- EditText フィールドに値を入力し、ボタンを選択します。
- フラグメント B が表示されます。デバイスの向きを 2 回変更して、戻るキーを押します。
- フラグメント A は空の EditText コントロールで表示されます。つまり、入力された値 (ビュー ステート) はそのままではありません。
何らかの理由で、setRetainInstance(true) を使用すると、方向が複数回変更されたときに (バックスタック上の) フラグメント A のビュー ステートが妨げられます。
考えられる説明
setRetainInstance の使い方がよくわからないうちに不安になったので、サポート ライブラリのソース コードを調べてみました。非常に高いレベルでは、これが setRetainInstance(true) で起こっていることだと思います。
フラグメント A が表示され、ボタンが押され、フラグメント A がフラグメント B に置き換えられます。このプロセスの一環として、FragmentManager (FM) はフラグメント A を削除し、
fragmentA.mRemoving
フラグを true に設定します。初めて方向を変更します。この時点で、FM はフラグメントのすべての状態を保存しようとします。
Parcelable saveAllState() { ... if (f.mState > Fragment.INITIALIZING && fs.mSavedFragmentState == null) { fs.mSavedFragmentState = saveFragmentBasicState(f);
フラグメント A には CREATED 状態があり、null 保存状態があるため、状態を保存する資格があります。
アクティビティは方向変更の一部として破棄されます。簡単に言うと、フラグメント A の状態が INITIALIZING に変更されました。
アクティビティが再作成され、フラグメントの状態を CREATED に移行しようとします。ただし、この時点で FM moveToState() メソッドにチェックがあります。
if (f.mRemoving && newState > f.mState) { // While removing a fragment, we can't change it to a higher state. newState = f.mState; }
フラグメントが保持された (再作成されなかった)ため、ステップ 1 から true のままであるため
fragmentA.mRemoving
、その状態は CREATED に増加せず、INITIALIZING 状態のままです。ここで戻るキーを押しても、手順 2 で状態が保存された結果、フラグメント A の状態はそのまま残ることに注意してください。2回目の方向転換。もう一度、FM はフラグメントのすべての状態を保存しようとします。
Parcelable saveAllState() { ... if (f.mState > Fragment.INITIALIZING && fs.mSavedFragmentState == null) { fs.mSavedFragmentState = saveFragmentBasicState(f);
ただし、フラグメント A は INITIALIZING 状態にあるため、状態を保存する資格がありません。したがって、オリエンテーションが 2 回目に完了すると、戻るキーが押された場合、フラグメント A の状態はそのままではなくなります。
質問
この動作は予期されたものですか? おそらく、これは setRetainInstance と backstack フラグメントの使用を推奨していないドキュメントに関連していますか?
ビュー ステートと setRetainInstance の使用をどのように処理する必要がありますか? おそらく私の使用例は正しくないのですが、この動作の違いで setRetainInstance 機能を使用するのは緊張するでしょう。
繰り返しますが、長い投稿で申し訳ありません。いつものようにフィードバックをお待ちしております。