LoaderManagerのinitLoaderとrestartLoaderの違い


129

との違いについて完全に迷っinitLoaderていますrestartLoader機能のますLoaderManager

  • 彼らは両方とも同じ署名を持っています。
  • restartLoader ローダーが存在しない場合は、それも作成します(「このマネージャーで新しいローダーを開始するか、既存のローダーを再起動します」)。

2つの方法の間に何らかの関係がありますか?呼び出しはrestartLoader常に呼び出しinitLoaderますか?電話しrestartLoaderなくても電話できますinitLoaderか?initLoader2回呼び出してデータを更新しても安全ですか?2つのうち1つを使用する必要があるのはなぜですか?

回答:


202

この質問に答えるには、LoaderManagerコードを掘り下げる必要があります。LoaderManager自体のドキュメントは十分に明確ではありませんが(またはこの質問はないでしょう)、抽象LoaderManagerのサブクラスであるLoaderManagerImplのドキュメントははるかにわかりやすくなっています。

initLoader

ローダーを使用して特定のIDを初期化するための呼び出し。このIDにすでにローダーが関連付けられている場合、ローダーは変更されず、以前のコールバックは新しく提供されたものに置き換えられます。現在IDのローダーがない場合は、新しいローダーが作成され、起動されます。

この関数は通常、コンポーネントの初期化時に使用し、依存するローダーが作成されるようにします。これにより、既存のローダーのデータが既に存在する場合にそれを再利用できます。たとえば、構成の変更後にアクティビティを再作成する場合、ローダーを再作成する必要はありません。

restartLoader

特定のIDに関連付けられたローダーを再作成するために呼び出します。このIDに関連付けられたローダーが現在存在する場合、必要に応じてキャンセル/停止/破棄されます。指定された引数を持つ新しいローダーが作成され、そのデータが利用可能になると、そのデータが配信されます。

[...]この関数を呼び出した後、このIDに関連付けられた以前のローダーは無効と見なされ、それ以降、それらからローダーはデータ更新を受け取りません。

基本的に2つのケースがあります。

  1. IDのローダーは存在しません。どちらの方法でも新しいローダーが作成されるため、違いはありません
  2. 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か?

はい。

initLoader2回呼び出してデータを更新しても安全ですか?

initLoader2回呼び出しても安全ですが、データは更新されません。

2つのうちの1つを使用する必要があるのはなぜですか?


上記の私の説明の後で、(うまくいけば)それは明確になるはずです。

構成変更

LoaderManagerは、構成の変更(方向の変更を含む)を行ってもその状態を保持するので、私たちがやるべきことは何も残っていないと思います。もう一度考えてみて...

まず、LoaderManagerはコールバックを保持しないため、何もしないと、コールバックメソッドなどの呼び出しを受信できなくonLoadFinished()なり、アプリが破損する可能性が高くなります。

したがって、少なくともinitLoaderコールバックメソッドを復元するために呼び出す必要があります(restartLoaderもちろん、これも可能です)。ドキュメントの状態:

呼び出しの時点で呼び出し元がその開始状態にあり、要求されたローダーがすでに存在し、そのデータを生成している場合、コールバックonLoadFinished(Loader, D)はすぐに呼び出されます(この関数内で)[...]。

つまりinitLoader、方向を変更した後に呼び出しを行うとonLoadFinished、データが既に読み込まれているため、すぐに呼び出しが行われます(変更前の場合)。それは簡単に聞こえるかもしれませんが、トリッキーになる可能性があります(Androidが大好きなわけではありません...)。

2つのケースを区別する必要があります。

  1. 構成変更自体を処理します。これは、setRetainInstance(true)を使用するフラグメントの場合、またはandroid:configChangesマニフェストに対応するタグを持つアクティビティの場合です。これらのコンポーネントは、画面の回転後などにonCreate呼び出しを受信しないため、initLoader/restartLoader別のコールバックメソッド(たとえば onActivityCreated(Bundle))で呼び出すことを覚えておいて ください。ローダーを初期化できるようにするには、ローダーIDを(たとえば、リストに)格納する必要があります。コンポーネントは設定を変更しても保持されるため、既存のローダーIDをループして呼び出しますinitLoader(loaderid, ...)
  2. 構成の変更自体は処理しません。この場合、ローダーはonCreateで初期化できますが、ローダーIDを手動で保持する必要があります。そうしないと、必要なinitLoader / restartLoader呼び出しを実行できません。IDがArrayListに格納されている場合は、initLoader呼び出しを行う前に、onSaveInstanceStateを
    outState.putIntegerArrayList(loaderIdsKey, loaderIdsArray)実行し、onCreate:でIDを復元します loaderIdsArray = savedInstanceState.getIntegerArrayList(loaderIdsKey)

:+1:最後の一点。initLoaderローテーション後にを使用した場合(およびすべてのコールバックが終了した場合、ローダーはアイドル状態です)、onLoadFinishedコールバックは取得できませんが、使用した場合は取得しますrestartLoaderか?
Blundell 2014年

不正解です。initLoaderメソッドは、戻る前にonLoadFinished()メソッドを呼び出します(ローダーが起動していてデータがある場合)。これをより詳細に説明するために、構成変更に関する段落を追加しました。
エマニュエルモエクリン2014年

6
もちろん、あなたの答えと@alexlockwoodの組み合わせにより、全体像がわかります。他の人の答えは、クエリが静的である場合はinitLoaderを使用し、クエリを変更する場合はrestartLoaderを使用することです
Blundell

1
「クエリが静的な場合はinitLoaderを使用し、クエリを変更する場合はrestartLoaderを使用する」
Emanuel Moecklin 14年

1
@Mhd。タハウィはコールバックを変更するのではなく、どこに行くかを設定するだけです。画面の回転後、Androidはメモリリークを防止するためにそれらを保持しないため、それらを再設定する必要があります。彼らが正しいことをする限り、あなたはそれらをあなたが望むものに自由に設定することができます。
エマニュエルモエクリン2017年

46

呼び出しinitLoaderLoaderがすでに作成されている場合(これは典型的には、例えば、構成変更後に発生)にLoaderの最新のデータを提供するLoaderManager伝えonLoadFinished、すぐに。ローダーがまだ作成されていない場合(たとえば、アクティビティ/フラグメントが最初に起動するとき)initLoaderへの呼び出しonCreateLoaderは、新しいローダーを作成するための呼び出しをLoaderManagerに指示します。

呼び出すとrestartLoader、既存のローダー(およびそれに関連付けられている既存のデータ)が破棄され、LoaderManagerに呼び出しonCreateLoaderて新しいローダーを作成し、新しいロードを開始するように指示します。


ドキュメントもこれについてはかなり明確です:

  • initLoaderローダーが初期化され、アクティブであることを確認します。ローダーがまだ存在しない場合は、ローダーが作成され、(アクティビティ/フラグメントが現在開始されている場合は)ローダーが起動します。それ以外の場合、最後に作成されたローダーが再利用されます。

  • restartLoaderこのマネージャーで新しいローダーを開始するか、既存のローダーを再起動し、そのローダーへのコールバックを登録し、(アクティビティ/フラグメントが現在開始されている場合)ローダーのロードを開始します。同じIDのローダーが以前に起動されている場合、新しいローダーが作業を完了すると、自動的に破棄されます。コールバックは、古いローダーが破棄される前に配信されます。


@TomanMoney私はそれが私の答えで何を意味するかを説明しました。どの部分が混乱していますか?
Alex Lockwood

ドキュメントを再ハッシュしました。しかし、このドキュメントでは、各メソッドをどこで使用する必要があるのか​​、そしてそれを台無しにするのがなぜ悪いのかについては何も示されていません。私の経験では、restartLoaderを呼び出すだけで、initLoaderを呼び出さないことで問題なく動作します。したがって、これはまだ混乱しています。
Tom anMoney 2013年

3
@TomanMoney通常、アクティビティ/フラグメントが最初に起動するときinitLoader()onCreate()/で使用しますonActivityCreated()。この方法では、ユーザーは最初のアクティビティを開いたとき、ローダーが初めて作成されます...しかし、全体の活動/フラグメントが破壊されている必要があり、後続の構成変更に、次の呼び出しには、initLoader()単に古い戻りますLoaderの代わりに新しいものを作成します。通常restartLoader()Loaderのクエリを変更する必要がある場合(つまり、フィルター処理/並べ替えられたデータを取得する場合など)を使用します。
Alex Lockwood

4
どちらのメソッドも同じシグネチャを持っているので、両方のメソッドを使用するというAPIの決定についてはまだ混乱しています。なぜAPIが毎回「正しいこと」を実行する単一のstartLoader()メソッドにならないのですか?多くの人を混乱させる部分だと思います。
Tom anMoney、2013

1
@TomanMoneyこのドキュメントには、developer.android.com/guide/components/loaders.htmlと記載されています。「構成変更後に再作成されると、最後のローダーのカーソルに自動的に再接続します。したがって、データを再クエリする必要はありません。」
IgorGanapolsky 2014

16

最近、複数のローダーマネージャーと画面の向きの変更に関する問題に遭遇しました。多くの試行錯誤の後、次のパターンがアクティビティとフラグメントの両方で機能することを伝えたいと思います。

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常に 1回実行され、restartLoaderが2回目に実行され、その後onResumeを通過するようにフラグを設定します

また、アクティビティ内のローダーごとに異なるIDを割り当てることを忘れないでください(番号付けに注意しないと、アクティビティ内のフラグメントに少し問題が生じる可能性があります)。


initLoaderのみを使用してみましたが、効果的に機能していないようです。

試してみましたinitLoaderのonCreateヌル引数を持つ(ドキュメントは、これがOKと言う)&restartLoader中(有効な引数付き)onResumeを ....ドキュメントが間違っている&initLoaderは NullPointer例外がスローされます。

試してみたrestartLoaderのみ...しばらく動作しますが、5番目または6番目の画面の向きを変更します。

onResumeinitLoaderを試しました。再びしばらくの間動作し、その後吹きます。(具体的には、「開始されていないときに呼び出された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);
    }
}

(私はスタックオーバーフローのどこかで見つけました)

繰り返しますが、これはしばらくはうまくいきましたが、それでも時々問題が発生しました。


デバッグ中に理解できることから、インスタンスの状態の保存/復元には、サイクルのスピンを生き残るためには、ライフサイクルのonCreate部分でinitLoader(/ s)を実行する必要があると思います。 。(私は間違っているかもしれません。)

別のマネージャーまたはタスクから結果が返されるまで開始できない(つまり、onCreateで初期化できない)マネージャーの場合は、initLoaderのみを使用します。(私はこれでは正しくないかもしれませんが、動作しているようです。これらのセカンダリローダーは即時のインスタンス状態の一部ではないため、この場合はinitLoaderの使用が実際に正しい場合があります)

ライフサイクル


図とドキュメントを見ると、initLoaderはアクティビティのonRestartのonCreate&restartLoaderに入る必要があると思いましたが、フラグメントがいくつかの異なるパターンを使用しているため、これが実際に安定しているかどうかを調べる時間はありませんでした。誰かがこの活動パターンで成功したかどうかコメントできますか?


/ @ Simonは100%正しいので、これが受け入れられる答えになるはずです。私は彼の答えを完全に信じていなかったので、この作業を行うためのさまざまな方法を見つけるために数時間を費やしました。initLoader呼び出しをonCreateに移動するとすぐに、物事が機能し始めました。その後、呼び出されたONSTART回のアカウントにワンショットフラグを必要としませんが、何のonCreate
CJS

2
「restartLoaderを試してみましたが...しばらく動作しますが、5番目または6番目の画面の向きが変わります。」します?私はそれを試してみて、画面を100回回転させましたが、爆破することはありませんでした。どのような例外が発生していますか?
Tom anMoney 2013年

-1私はこの回答の背後にある研究努力に感謝しますが、ほとんどの結果は正しくありません。
エマニュエルモエクリン2014年

1
@IgorGanapolskyほとんどすべて。私の答えを読んで理解すれば、initLoaderとrestartLoaderが何をするのか、いつ使用するのかが理解でき、Simonの結論のほとんどすべてが間違っている理由も理解できます。フラグメント/アクティビティーのライフサイクルとinitLoader / restartLoaderをいつ使用するかの決定との間には関係がありません(構成の変更で説明する警告があります)。サイモンは試行錯誤の結果、ライフサイクルが2つの方法を理解するための手がかりであると結論付けていますが、そうではありません。
エマニュエルモエクリン2014

@IgorGanapolsky私は自分の答えを宣伝するつもりはありません。私は単に他の開発者を助け、彼らが自分のアプリにSimonの結果を使用するのを妨げようとしているだけです。2つの方法が何を意味するのかを理解すると、全体がかなり明白になり、実装が簡単になります。
エマニュエルモエクリン2014

0

initLoaderローダーが既に存在する場合は、同じパラメーターを再利用します。古いデータが既にロードされている場合は、新しいパラメーターで呼び出してもすぐに戻ります。ローダーは新しいデータのアクティビティを自動的に通知するのが理想的です。画面が回転した場合、initLoader再び呼び出され、古いデータがすぐに表示されます。

restartLoader強制的にリロードし、パラメータも変更する場合に使用します。ローダーを使用してログイン画面を作成する場合はrestartLoader、ボタンがクリックされるたびにのみ呼び出します。(認証情報が正しくないなどの理由で、ボタンが複数回クリックされる場合があります)。initLoaderログインの進行中に画面が回転した場合に、アクティビティの保存されたインスタンスの状態を復元するときにのみ呼び出します。


-1

ローダーが既に存在する場合、restartLoaderは古いローダーを停止/キャンセル/破棄しますが、initLoaderは指定されたコールバックで初期化するだけです。これらのケースで古いコールバックが何をするのかはわかりませんが、破棄されるだけだと思います。

http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/4.0.1_r1/android/app/LoaderManager.javaをスキャンしましたが、正確な情報がわかりません違いは、方法が異なることを除けばです。つまり、最初にinitLoaderを使用し、その後は再起動しますが、それぞれが正確に何を行うかは確実には言えません。


そしてinitLoader、この場合何をしますか?
theomega 2013年

-1

最初の起動時の初期ローダーはloadInBackground()メソッドを使用し、2回目の起動時には省略されます。だから、私の意見では、より良い解決策は:

Loader<?> loa; 
try {
    loa = getLoaderManager().getLoader(0);
} catch (Exception e) {
    loa = null;
}
if (loa == null) {
    getLoaderManager().initLoader(0, null, this);
} else {
    loa.forceLoad();
}

////////////////////////////////////////////////// /////////////////////////

protected SimpleCursorAdapter mAdapter;

private abstract class SimpleCursorAdapterLoader 
    extends AsyncTaskLoader <Cursor> {

    public SimpleCursorAdapterLoader(Context context) {
        super(context);
    }

    @Override
    protected void onStartLoading() {
        if (takeContentChanged() || mAdapter.isEmpty()) {
            forceLoad();
        }
    }

    @Override
    protected void onStopLoading() {
        cancelLoad();
    }

    @Override
    protected void onReset() {
        super.onReset();
        onStopLoading();
    }
}

私はこのソリューションを見つけるために多くの時間を費やしました-私の場合、restartLoader(...)は適切に機能しませんでした。唯一のforceLoad()は、コールバックなしで前のロードスレッドを終了させ(すべてのdbトランザクションが適切に終了するようにします)、新しいスレッドを再び開始します。はい、少し時間がかかりますが、より安定しています。最後に開始されたスレッドのみがコールバックを受け取ります。したがって、dbトランザクションを中断してテストを行う場合は、ようこそ、restartLoader(...)を試してください。それ以外の場合は、forceLoad()を使用してください。restartLoader(...)の唯一の便利な点は、新しい初期データ、つまりパラメーターを提供することです。この場合、適切なフラグメントのonDetach()メソッドでローダーを破棄することを忘れないでください。また、時々、あなたが活動をしているときに、彼らに言わせて、ローダーを含む2つのフラグメント(それぞれ1つの包括的なアクティビティ)-ローダーマネージャーは2つしか到達しないため、アクティビティはローダーのフラグメントとフラグメントを共有します。これは、ロード中に最初に画面に表示されます。LoaderManager.enableDebugLogging(true);を試してください。特定のケースごとに詳細を確認します。


2
-1への呼び出しをラップするためgetLoader(0)try { ... } catch (Exception e) { ... }
アレックスロックウッド
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.