128

initLoaderと のrestartLoader機能の違いに関して、私は完全に迷っていますLoaderManager

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

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

4

6 に答える 6

201

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

initLoader

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

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

再起動ローダー

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

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

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

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

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

ローダーが既に作成されているときに呼び出すinitLoaderと (これは通常、構成の変更後などに発生します)、ローダーの最新のデータをonLoadFinishedすぐに配信するように LoaderManager に指示します。ローダーがまだ作成されていない場合 (たとえば、アクティビティ/フラグメントが最初に起動したとき) への呼び出しは、新しいローダーを作成するためinitLoaderに呼び出すように LoaderManager に指示します。onCreateLoader

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


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

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

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

于 2013-05-23T04:47:27.803 に答える
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 がonResumeを介した2 回目以降のパスで実行されるようにします)

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


私はinitLoaderのみを使用してみました .... 効果的に動作していないようでした。

null引数を使用してonCreateでinitLoaderを試しました(ドキュメントはこれで問題ないと言っています)& onResumeでrestartLoader(有効な引数を使用) ....ドキュメントが間違っていて、 initLoaderがnullpointer例外をスローします。

restartLoaderのみを試しました ... しばらくは機能しますが、5 番目または 6 番目の画面の向きを変更すると吹き飛ばされます。

onResumeでinitLoaderを試しました。再びしばらくの間機能し、その後吹き飛ばします。(具体的には、「開始されていないときに 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 (/s) を実行する必要があるインスタンス状態の保存/復元に関係があると思います. (私が間違っているかもしれません。)

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

ライフサイクル


ダイアグラムとドキュメントを見ると、initLoader は onCreate & restartLoader の onRestart for Activities に入れるべきだと思っていたでしょうが、それでは Fragments が別のパターンを使用しているため、これが実際に安定しているかどうかを調査する時間がありませんでした。このパターンのアクティビティで成功したかどうかについて、他の誰かがコメントできますか?

于 2013-05-22T23:51:33.150 に答える
0

最初の起動時の初期化ローダーは 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(...) の唯一の便利な点は、新しい初期データ、つまりパラメーターを提供することです。この場合、適切な Fragment の onDetach() メソッドでローダーを破棄することを忘れないでください。また、活動をしているときに、Loader を含む 2 つのフラグメント、それぞれ 1 つの包括的なアクティビティ -- 2 つの Loader Manager のみに到達するため、Activity はその LoaderManager を Fragment (s) と共有し、読み込み中に最初に画面に表示されます。LoaderManager.enableDebugLogging(true); を試してください。特定のケースごとに詳細を表示します。

于 2013-03-19T14:21:53.567 に答える
0

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

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

于 2014-01-21T14:21:13.910 に答える
-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を使用し、次の時間は再起動しますが、それぞれが正確に何をするかははっきりとは言えません。

于 2013-01-21T18:43:55.553 に答える