initLoader
とrestartLoader
のLoaderManager
関数の違いに関して、私は完全に迷っています。
restartLoader
は、存在しない場合もローダーを作成します(「このマネージャーで新しいローダーを起動するか、既存のローダーを再起動します」)。2つの方法の間に何らかの関係がありますか? restartLoader
を呼び出すと、常にinitLoader
を呼び出しますか? restartLoader
を呼び出さずにinitLoader
を呼び出すことはできますか?データを更新するためにinitLoader
を2回呼び出すのは保存ですか?いつ2つのうちの1つを使用する必要があり、(重要!)なぜですか?
この質問に答えるには、LoaderManagerコードを掘り下げる必要があります。 LoaderManager自体のドキュメントは十分に明確ではありませんが(または、この質問はありません)、抽象的なLoaderManagerのサブクラスであるLoaderManagerImplのドキュメントは、はるかに啓発的です。
initLoader
ローダーで特定のIDを初期化するために呼び出します。このIDに既にローダーが関連付けられている場合、それは変更されずに残り、以前のコールバックは新しく提供されたものに置き換えられます。現在IDのローダーがない場合は、新しいローダーが作成されて開始されます。
通常、この関数は、コンポーネントが初期化するときに使用し、コンポーネントが依存するローダーが作成されるようにします。これにより、既存のローダーのデータが既にある場合はそれを再利用できるため、たとえば、構成の変更後にアクティビティを再作成する場合、ローダーを再作成する必要はありません。
restartLoader
特定のIDに関連付けられたローダーを再作成するための呼び出し。現在、このIDに関連付けられているローダーがある場合、必要に応じてキャンセル/停止/破棄されます。指定された引数を持つ新しいローダーが作成され、そのデータが利用可能になると配信されます。
[...]この関数を呼び出した後、このIDに関連付けられた以前のローダーは無効とみなされ、それらからのさらなるデータ更新は受信しません。
基本的に2つのケースがあります。
両方のメソッドの簡略化されたコードは次のとおりです。
initLoader
LoaderInfo info = mLoaders.get(id);
if (info == null) {
// Loader doesn't already exist -> create new one
info = createAndInstallLoader(id, args, LoaderManager.LoaderCallbacks<Object>)callback);
} else {
// Loader exists -> only replace callbacks
info.mCallbacks = (LoaderManager.LoaderCallbacks<Object>)callback;
}
restartLoader
LoaderInfo info = mLoaders.get(id);
if (info != null) {
LoaderInfo inactive = mInactiveLoaders.get(id);
if (inactive != null) {
// does a lot of stuff to deal with already inactive loaders
} else {
// Keep track of the previous instance of this loader so we can destroy
// it when the new one completes.
info.mLoader.abandon();
mInactiveLoaders.put(id, info);
}
}
info = createAndInstallLoader(id, args, (LoaderManager.LoaderCallbacks<Object>)callback);
ローダーが存在しない場合(info == null)にわかるように、両方のメソッドは新しいローダーを作成します(info = createAndInstallLoader(...))。ローダーが既に存在する場合、initLoaderはコールバック(info.mCallbacks = ...)のみを置き換え、restartLoaderは古いローダーを非アクティブにし(新しいローダーが作業を完了すると破棄されます)、新しいものを作成します。
したがって、initLoaderを使用するタイミングとrestartLoaderを使用するタイミング、および2つのメソッドを使用することが理にかなっている理由が明らかになったと述べました。 initLoaderは、初期化されたローダーがあることを確認するために使用されます。存在しない場合は新しいものが作成され、既に存在する場合は再利用されます。実行するクエリ(基本データではなく、CursorLoaderのSQLステートメントのような実際のクエリ)が変更されたため、新しいローダーが必要でない限り、常にこのメソッドを使用します。この場合、restartLoaderを呼び出します。
アクティビティ/フラグメントのライフサイクルは、いずれかの方法を使用する決定とは関係ありません(サイモンが提案したように、ワンショットフラグを使用して呼び出しを追跡する必要はありません)!この決定は、新しいローダーの「必要性」のみに基づいて行われます。同じクエリを実行する場合はinitLoaderを使用し、別のクエリを実行する場合はrestartLoaderを使用します。常にrestartLoaderを使用できますが、それは非効率的です。画面の回転後、またはユーザーがアプリから離れて同じアクティビティに戻った場合、通常は同じクエリ結果を表示したいため、restartLoaderはローダーを不必要に再作成し、基になる(潜在的に高価な)クエリ結果を破棄します。
ロードされるデータと、そのデータをロードするための「クエリ」の違いを理解することは非常に重要です。注文のためにテーブルをクエリするCursorLoaderを使用すると仮定しましょう。そのテーブルに新しい注文が追加されると、CursorLoaderはonContentChanged()を使用して、新しい注文を更新して表示するようにUIに通知します(この場合、restartLoaderを使用する必要はありません)。未処理の注文のみを表示する場合は、新しいクエリが必要です。restartLoaderを使用して、新しいクエリを反映する新しいCursorLoaderを返します。
2つの方法の間に何らかの関係がありますか?
彼らはコードを共有して新しいローダーを作成しますが、ローダーが既に存在する場合は異なることをします。
RestartLoaderの呼び出しは常にinitLoaderを呼び出しますか?
いいえ、決してありません。
InitLoaderを呼び出さずにrestartLoaderを呼び出すことはできますか?
はい。
InitLoaderを2回呼び出してデータを更新しても安全ですか?
InitLoaderを2回呼び出すのは安全ですが、データは更新されません。
いつ2つのうちの1つを使用する必要があり、(重要!)なぜですか?
上記の説明の後、それが(できれば)明確になるはずです。
構成の変更
LoaderManagerは、構成の変更(向きの変更を含む)にわたってその状態を保持するため、私たちにできることは何もないと思われます。もう一度考えて...
まず第一に、LoaderManagerはコールバックを保持しないため、何もしなければonLoadFinished()などのコールバックメソッドの呼び出しを受け取らず、アプリを中断する可能性が非常に高くなります。したがって、少なくともinitLoaderを呼び出してコールバックメソッドを復元する必要があります(もちろん、restartLoaderも可能です)。 ドキュメント 状態:
呼び出し時点で呼び出し元が開始状態にあり、要求されたローダーが既に存在し、そのデータを生成している場合、コールバックonLoadFinished(Loader、D)がすぐに呼び出されます(この関数内)[...]。
つまり、向きの変更後にinitLoaderを呼び出すと、データが既に読み込まれているため、すぐにonLoadFinished呼び出しが取得されます(変更前の場合を想定)。簡単に聞こえますが、注意が必要です(Androidが大好きではないでしょうか...).
2つのケースを区別する必要があります。
ローダーが既に作成されているときにinitLoader
を呼び出す(これは通常、構成の変更後などに発生します)LoaderManagerに、ローダーの最新データをonLoadFinished
にすぐに配信するように指示します。ローダーがまだ作成されていない場合(アクティビティ/フラグメントが最初に起動するときなど)、initLoader
への呼び出しはLoaderManagerにonCreateLoader
を呼び出して新しいローダーを作成するように指示します。
restartLoader
を呼び出すと、既存のローダー(およびそれに関連付けられている既存のデータ)が破棄され、LoaderManagerにonCreateLoader
を呼び出して新しいローダーを作成し、新しいロードを開始するように指示します。
ドキュメントもこれについてかなり明確です:
initLoader
は、ローダーが初期化されてアクティブであることを確認します。ローダーがまだ存在しない場合は、ローダーが作成され、(アクティビティ/フラグメントが現在開始されている場合)ローダーが開始されます。そうでない場合、最後に作成されたローダーが再利用されます。
restartLoader
は、このマネージャーで新しいローダーを起動するか、既存のローダーを再起動し、コールバックを登録して、(アクティビティ/フラグメントが現在開始されている場合)ロードを開始します。同じIDのローダーが以前に起動されている場合、新しいローダーが作業を完了すると自動的に破棄されます。コールバックは、古いローダーが破棄される前に配信されます。
最近、複数のローダーマネージャーと画面の向きの変更で問題が発生しました。多くの試行錯誤の後、アクティビティとフラグメントの両方で次のパターンが機能すると言いたいと思います。
onCreate: call initLoader(s)
set a one-shot flag
onResume: call restartLoader (or later, as applicable) if the one-shot is not set.
unset the one-shot in either case.
(つまり、initLoader)がalways一度だけ実行され、restartLoaderが実行されるようにフラグを設定します2番目以降のパススルーonResume)
また、アクティビティ内のローダーごとに異なるIDを割り当てることを忘れないでください(番号付けに注意しないと、そのアクティビティ内のフラグメントで少し問題になる可能性があります)
initLoaderのみを使用してみました....効果的に機能しないようでした。
ヌル引数でonCreateでinitLoaderを試しました(これは大丈夫と言います) &restartLoader(有効な引数付き)inonResume.... docs間違っている&initLoaderはnullpointer例外をスローします。
試してみましたrestartLoaderのみ...しばらくは機能しますが、5番目または6番目の画面の向きが変更されます。
試してみましたinitLoaderinonResume;再びしばらく動作し、その後吹きます。 (具体的には、「開始されていないときにdoRetainが呼び出されました:」エラー)
以下を試してみました:(コンストラクタに渡されたローダーIDを持つカバークラスからの抜粋)
/**
* start or restart the loader (why bother with 2 separate functions ?) (now I know why)
*
* @param manager
* @param args
* @deprecated use {@link #restart(LoaderManager, Bundle)} in onResume (as appropriate) and {@link #initialise(LoaderManager, Bundle)} in onCreate
*/
@Deprecated
public void start(LoaderManager manager, Bundle args) {
if (manager.getLoader(this.id) == null) {
manager.initLoader(this.id, args, this);
} else {
manager.restartLoader(this.id, args, this);
}
}
(Stack-Overflowのどこかにありました)
繰り返しますが、これはしばらくは機能しましたが、それでも時折不具合が発生しました。
結果が別のマネージャーまたはタスクから返されるまで開始できないマネージャーの場合(つまり、onCreateで初期化できない)、 initLoader。 (これは正しくないかもしれませんが、動作しているようです。これらのセカンダリローダーは即時インスタンス状態の一部ではないため、initLoaderを実際に使用できますこの場合は正しい)
ダイアグラムとドキュメントを見ると、initLoaderはアクティビティのonRestartでonCreate&restartLoaderに入るべきだと思っていましたが、フラグメントはいくつかの異なるパターンを使用するため、これが実際に安定しているかどうかを調べる時間はありませんでした。アクティビティのこのパターンで成功した場合、他の誰かがコメントできますか?
ローダーが既に存在する場合、initLoader
は同じパラメーターを再利用します。古いパラメーターが既に読み込まれている場合、新しいパラメーターで呼び出した場合でも、すぐに戻ります。ローダーは、理想的には新しいデータのアクティビティを自動的に通知する必要があります。画面が回転すると、initLoader
が再度呼び出され、古いデータがすぐに表示されます。
restartLoader
は、リロードを強制してパラメーターも変更する場合に使用します。ローダーを使用してログイン画面を作成する場合、ボタンがクリックされるたびにrestartLoader
のみを呼び出します。 (間違った資格情報などにより、ボタンが複数回クリックされる場合があります。)ログインの進行中に画面が回転した場合にアクティビティの保存されたインスタンスの状態を復元するときにのみ、initLoader
を呼び出します。