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