LoaderManager
この質問に答えるには、コードを掘り下げる必要があります。LoaderManager 自体のドキュメントは十分に明確ではありませんが (または、この質問はありません)、抽象 LoaderManager のサブクラスである LoaderManagerImpl のドキュメントは、はるかに啓発的です。
initLoader
Loader で特定の ID を初期化するために呼び出します。この ID にすでにローダーが関連付けられている場合は、ID は変更されずにそのまま残り、以前のコールバックは新しく提供されたコールバックに置き換えられます。ID のローダーが現在存在しない場合は、新しいローダーが作成されて開始されます。
通常、この関数は、コンポーネントが依存するローダーが確実に作成されるように、コンポーネントの初期化時に使用する必要があります。これにより、既存のローダーのデータが既に存在する場合は再利用できるため、たとえば、構成の変更後にアクティビティが再作成されたときに、ローダーを再作成する必要がなくなります。
再起動ローダー
特定の ID に関連付けられたローダーを再作成するために呼び出します。現在、この ID に関連付けられているローダーがある場合は、必要に応じてキャンセル/停止/破棄されます。指定された引数を持つ新しいローダーが作成され、そのデータが利用可能になると配信されます。
[...] この関数を呼び出した後、この ID に関連付けられた以前のローダーは無効と見なされ、それらからデータ更新を受け取ることはありません。
基本的に次の 2 つのケースがあります。
- ID のローダーが存在しません。どちらの方法でも新しいローダーが作成されるため、違いはありません。
- ID のローダーは既に存在します:
initLoader
パラメーターとして渡されたコールバックのみを置き換えますが、ローダーをキャンセルまたは停止しません。a の場合CursorLoader
、カーソルが開いてアクティブなままであることを意味します (initLoader
呼び出し前の場合)。一方、restartLoader は、ローダーをキャンセル、停止、および破棄し (そして、カーソルのように基礎となるデータ ソースを閉じます)、新しいローダーを作成します (これにより、新しいカーソルも作成され、ローダーがCursorLoader)。
両方の方法の簡略化されたコードは次のとおりです。
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;
}
再起動ローダー
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
ますか?
いいえ、決してそうではありません。
電話restartLoader
しなくても電話できますinitLoader
か?
はい。
initLoader
データを更新するために 2 回呼び出しても安全ですか?
2 回呼び出しても安全ですinitLoader
が、データは更新されません。
いつ2つのうちの1つを使用する必要があり、その理由は?
上記の説明の後、それは(うまくいけば)明確になるはずです。
構成の変更
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,
...)
。
- 構成の変更自体を処理しません。この場合、ローダーは onCreate で初期化できますが、ローダー ID を手動で保持する必要があります。そうしないと、必要な initLoader/restartLoader 呼び出しを行うことができません。ID が ArrayList に格納されている場合は、initLoader 呼び出しを行う前に、
outState.putIntegerArrayList(loaderIdsKey, loaderIdsArray)
in onSaveInstanceState を実行し、onCreate: で ID を復元します
。loaderIdsArray =
savedInstanceState.getIntegerArrayList(loaderIdsKey)