initLoader
とrestartLoader
の機能の違いが全く分かりませんLoaderManager
。
- どちらも同じ署名を持っています。
restartLoader
ローダーが存在しない場合は、ローダーも作成します (「このマネージャーで新しいローダーを開始するか、既存のローダーを再起動します」)。
2つの方法の間には何らかの関係がありますか? を呼び出すとrestartLoader
常に が呼び出されますか?を呼び出さなくてもinitLoader
を呼び出すことはできますか? データを更新するために 2 回呼び出しても安全ですか? 2 つの方法のうち 1 つをいつ使用すべきですか?restartLoader
initLoader
initLoader
なぜ?
ベストアンサー1
この質問に答えるには、コードを詳しく調べる必要がありますLoaderManager
。LoaderManager 自体のドキュメントは十分に明確ではありませんが (そうでなければこの質問は発生しません)、抽象 LoaderManager のサブクラスである LoaderManagerImpl のドキュメントは、はるかにわかりやすいです。
初期化ローダー
Loader を使用して特定の ID を初期化するために呼び出します。この ID にすでに Loader が関連付けられている場合は、変更されずに、以前のコールバックが新しく提供されたものに置き換えられます。現在 ID の Loader が存在しない場合は、新しい Loader が作成され、開始されます。
この関数は通常、コンポーネントの初期化時に使用して、コンポーネントが依存する Loader が作成されるようにします。これにより、既存の Loader のデータが存在する場合にそれを再利用できるため、たとえば構成変更後に Activity が再作成されるときに、その Loader を再作成する必要がなくなります。
再起動ローダー
特定の ID に関連付けられた Loader を再作成するには、呼び出します。現在この ID に関連付けられた Loader がある場合は、必要に応じてキャンセル/停止/破棄されます。指定された引数を持つ新しい Loader が作成され、そのデータが利用可能になると配信されます。
[...] この関数を呼び出すと、この ID に関連付けられている以前の Loader は無効とみなされ、それ以降のデータ更新は受信されなくなります。
基本的には2つのケースがあります。
- ID を持つローダーが存在しない: どちらの方法でも新しいローダーが作成されるため、違いはありません。
- ID のローダーが既に存在する場合:
initLoader
は、パラメーターとして渡されたコールバックを置き換えるだけで、ローダーをキャンセルまたは停止しません。つまりCursorLoader
、カーソルは開いたままアクティブになります (呼び出し前の場合initLoader
)。一方、`restartLoader は、ローダーをキャンセル、停止、破棄し (カーソルのように基になるデータ ソースを閉じ)、新しいローダーを作成します (ローダーが CursorLoader の場合は、新しいカーソルも作成され、クエリが再実行されます)。
両方の方法の簡略化されたコードは次のとおりです。
初期化ローダー
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;
}
再起動ローダー
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
古いローダーを非アクティブ化してから (新しいローダーが作業を完了すると破棄されます)、新しいローダーを作成します。
したがって、 と をいつ使用するか、また、なぜこの 2 つのメソッドを持つことが理にかなっているかが明確になりましたinitLoader
。restartLoader
は、initLoader
初期化されたローダーがあることを確認するために使用されます。 存在しない場合は新しいローダーが作成され、すでに存在する場合はそれが再利用されます。 実行するクエリが変更されたために新しいローダーが必要になった場合を除き、常にこのメソッドを使用します (基礎データではなく、CursorLoader の SQL ステートメントのような実際のクエリ)。 変更された場合は、 を呼び出しますrestartLoader
。
のアクティビティ/フラグメントのライフサイクルは、どちらかの方法を使用するという決定とは無関係です (また、Simon が提案したように、ワンショット フラグを使用して呼び出しを追跡する必要もありません)。この決定は、新しいローダーの「必要性」に基づいてのみ行われます。同じクエリを実行する場合は を使用しinitLoader
、別のクエリを実行する場合は を使用しますrestartLoader
。
常に を使用することもできますrestartLoader
が、非効率的です。画面の回転後、またはユーザーがアプリから移動して後で同じ Activity に戻った場合、通常は同じクエリ結果を表示するため、 はrestartLoader
ローダーを不必要に再作成し、基礎となる (潜在的にコストの高い) クエリ結果を閉じます。
ロードされるデータと、そのデータをロードするための「クエリ」の違いを理解することは非常に重要です。テーブルで注文をクエリする CursorLoader を使用すると仮定します。そのテーブルに新しい注文が追加されると、CursorLoader は onContentChanged() を使用して UI に更新を通知し、新しい注文を表示します (restartLoader
この場合は を使用する必要はありません)。オープン注文のみを表示する場合は、新しいクエリが必要であり、 を使用してrestartLoader
新しいクエリを反映する新しい CursorLoader を返します。
2つの方法の間には何らかの関係があるのでしょうか?
これらは新しい Loader を作成するためのコードを共有していますが、Loader がすでに存在する場合は異なる処理を実行します。
電話は
restartLoader
常に電話ですかinitLoader
?
いいえ、決してそうではありません。
restartLoader
電話をかけなくても電話できますかinitLoader
?
はい。
initLoader
データを更新するために 2 回呼び出しても安全ですか?
2 回呼び出しても安全ですinitLoader
が、データは更新されません。
どちらを使うべきかなぜ?
上記の説明を読めば、それが(おそらく)明らかになるはずです。
設定の変更
LoaderManager は、構成の変更 (方向の変更を含む) 後も状態を保持するため、私たちにできることは何も残っていないと思われるかもしれません。もう一度考えてみてください...
まず、LoaderManager はコールバックを保持しないため、何もしないと、などのコールバック メソッドへの呼び出しが受信されずonLoadFinished()
、アプリが壊れる可能性が高くなります。
したがって、少なくともinitLoader
コールバックメソッドを復元するためには、を呼び出す必要があります(restartLoader
もちろん、も可能です)。ドキュメンテーション状態:
呼び出し時点で呼び出し元が開始状態にあり、要求されたローダーがすでに存在し、データを生成している場合は、コールバックが
onLoadFinished(Loader, D)
直ちに呼び出されます (この関数内で) [...]。
つまり、向きの変更後に呼び出すとinitLoader
、データがすでに読み込まれているため、すぐに呼び出しが行われますonLoadFinished
(変更前はそうであったと仮定)。これは簡単に聞こえますが、難しい場合があります (私たちは皆、Android が大好きですよね...)。
次の 2 つのケースを区別する必要があります。
- 構成の変更を自身で処理します。これは、setRetainInstance(true) を使用するフラグメント、または
android:configChanges
マニフェスト内の対応するタグを持つアクティビティの場合です。これらのコンポーネントは、画面の回転後などに onCreate 呼び出しを受け取らないため、initLoader/restartLoader
別のコールバック メソッド ( などonActivityCreated(Bundle)
) で呼び出すことを忘れないでください。ローダーを初期化するには、ローダー ID を保存する必要があります (リストなど)。コンポーネントは構成の変更をまたいで保持されるため、既存のローダー ID をループして を呼び出すことができますinitLoader(loaderid, ...)
。 - 構成の変更自体を処理しない: この場合、Loader は onCreate で初期化できますが、Loader ID を手動で保持する必要があります。そうしないと、必要な initLoader/restartLoader 呼び出しを行うことができません。ID が ArrayList に格納されている場合は、initLoader 呼び出しを行う前に
outState.putIntegerArrayList(loaderIdsKey, loaderIdsArray)
、onSaveInstanceState で を実行し、onCreate: で ID を復元します。loaderIdsArray = savedInstanceState.getIntegerArrayList(loaderIdsKey)