Um diese Frage zu beantworten, müssen Sie sich in den LoaderManager
Code vertiefen. Während die Dokumentation für LoaderManager selbst nicht klar genug ist (oder es würde diese Frage nicht geben), ist die Dokumentation für LoaderManagerImpl, eine Unterklasse des abstrakten LoaderManager, viel aufschlussreicher.
initLoader
Rufen Sie an, um eine bestimmte ID mit einem Loader zu initialisieren. Wenn dieser ID bereits ein Loader zugeordnet ist, bleibt sie unverändert, und alle vorherigen Rückrufe werden durch die neu bereitgestellten ersetzt. Wenn derzeit kein Loader für die ID vorhanden ist, wird ein neuer erstellt und gestartet.
Diese Funktion sollte im Allgemeinen beim Initialisieren einer Komponente verwendet werden, um sicherzustellen, dass ein Loader erstellt wird, auf den sie sich verlässt. Auf diese Weise können vorhandene Daten eines Loaders wiederverwendet werden, sofern bereits eine vorhanden ist. Wenn beispielsweise eine Aktivität nach einer Konfigurationsänderung neu erstellt wird, müssen die Loader nicht neu erstellt werden.
restartLoader
Rufen Sie an, um den einer bestimmten ID zugeordneten Loader neu zu erstellen. Wenn dieser ID derzeit ein Loader zugeordnet ist, wird dieser entsprechend abgebrochen / gestoppt / zerstört. Ein neuer Loader mit den angegebenen Argumenten wird erstellt und seine Daten werden Ihnen zugestellt, sobald sie verfügbar sind.
[...] Nach dem Aufruf dieser Funktion werden alle vorherigen Loader, die dieser ID zugeordnet sind, als ungültig betrachtet, und Sie erhalten keine weiteren Datenaktualisierungen von ihnen.
Grundsätzlich gibt es zwei Fälle:
- Der Loader mit der ID existiert nicht: Beide Methoden erstellen einen neuen Loader, sodass dort kein Unterschied besteht
- Der Loader mit der ID ist bereits vorhanden:
initLoader
Ersetzt nur die als Parameter übergebenen Rückrufe, bricht den Loader jedoch nicht ab oder stoppt ihn nicht. Für a CursorLoader
bedeutet dies, dass der Cursor offen und aktiv bleibt (falls dies vor dem initLoader
Aufruf der Fall war ). `restartLoader hingegen bricht den Loader ab, stoppt und zerstört ihn (und schließt die zugrunde liegende Datenquelle wie einen Cursor) und erstellt einen neuen Loader (der auch einen neuen Cursor erstellen und die Abfrage erneut ausführen würde, wenn der Loader dies ist ein CursorLoader).
Hier ist der vereinfachte Code für beide Methoden:
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);
Wie wir sehen können, erstellen beide Methoden einen neuen Loader (info = createAndInstallLoader (...)), falls der Loader nicht vorhanden ist (info == null). Falls der Loader bereits vorhanden ist, werden initLoader
nur die Rückrufe (info.mCallbacks = ...) ersetzt, während restartLoader
der alte Loader inaktiviert wird (er wird zerstört, wenn der neue Loader seine Arbeit beendet) und anschließend ein neuer erstellt.
Somit ist jetzt klar, wann initLoader
und wann zu verwenden ist restartLoader
und warum es sinnvoll ist, die beiden Methoden zu verwenden.
initLoader
wird verwendet, um sicherzustellen, dass ein initialisierter Loader vorhanden ist. Wenn keine vorhanden ist, wird eine neue erstellt. Wenn eine bereits vorhanden ist, wird sie wiederverwendet. Wir verwenden diese Methode immer, es sei denn, wir benötigen einen neuen Loader, da sich die auszuführende Abfrage geändert hat (nicht die zugrunde liegenden Daten, sondern die tatsächliche Abfrage wie in der SQL-Anweisung für einen CursorLoader). In diesem Fall rufen wir auf restartLoader
.
Der Aktivitäts- / Fragment-Lebenszyklus hat nichts mit der Entscheidung zu tun, die eine oder andere Methode zu verwenden (und es ist nicht erforderlich, die Anrufe mithilfe eines One-Shot-Flags zu verfolgen, wie von Simon vorgeschlagen)! Diese Entscheidung wird ausschließlich aufgrund der "Notwendigkeit" eines neuen Laders getroffen. Wenn wir dieselbe Abfrage ausführen möchten, die wir verwenden initLoader
, wenn wir eine andere Abfrage ausführen möchten, die wir verwenden restartLoader
.
Wir könnten immer verwenden, restartLoader
aber das wäre ineffizient. Nach einer Bildschirmrotation oder wenn der Benutzer von der App weg navigiert und später zu derselben Aktivität zurückkehrt, möchten wir normalerweise dasselbe Abfrageergebnis anzeigen. Daher restartLoader
würde der Loader unnötig neu erstellt und das zugrunde liegende (möglicherweise teure) Abfrageergebnis verworfen.
Es ist sehr wichtig, den Unterschied zwischen den geladenen Daten und der "Abfrage" zum Laden dieser Daten zu verstehen. Nehmen wir an, wir verwenden einen CursorLoader, der eine Tabelle nach Bestellungen abfragt. Wenn dieser Tabelle eine neue Reihenfolge hinzugefügt wird, verwendet der CursorLoader onContentChanged (), um die Benutzeroberfläche zu informieren, die neue Reihenfolge zu aktualisieren und anzuzeigen ( restartLoader
in diesem Fall nicht erforderlich ). Wenn wir nur offene Aufträge anzeigen möchten, benötigen wir eine neue Abfrage und restartLoader
geben einen neuen CursorLoader zurück, der die neue Abfrage widerspiegelt.
Gibt es eine Beziehung zwischen den beiden Methoden?
Sie teilen den Code, um einen neuen Loader zu erstellen, aber sie tun verschiedene Dinge, wenn bereits ein Loader vorhanden ist.
Ruft der Anruf restartLoader
immer an initLoader
?
Nein, das tut es nie.
Kann ich anrufen, restartLoader
ohne anrufen zu müssen initLoader
?
Ja.
Ist es sicher, initLoader
zweimal anzurufen , um die Daten zu aktualisieren?
Es ist sicher, initLoader
zweimal anzurufen, aber es werden keine Daten aktualisiert.
Wann sollte ich eine der beiden verwenden und warum ?
Das sollte (hoffentlich) nach meinen obigen Erklärungen klar sein.
Konfigurationsänderungen
Ein LoaderManager behält seinen Status bei Konfigurationsänderungen (einschließlich Orientierungsänderungen) bei, sodass Sie denken, dass wir nichts mehr zu tun haben. Denk nochmal...
Erstens behält ein LoaderManager die Rückrufe nicht bei. Wenn Sie also nichts tun, erhalten Sie keine Anrufe zu Ihren Rückrufmethoden wie onLoadFinished()
und dergleichen, und das wird Ihre App sehr wahrscheinlich beschädigen.
Daher MÜSSEN wir zumindest aufrufen initLoader
, um die Rückrufmethoden wiederherzustellen (a restartLoader
ist natürlich auch möglich). In der Dokumentation heißt es:
Befindet sich der Anrufer zum Zeitpunkt des Anrufs im Startzustand und der angeforderte Loader existiert bereits und hat seine Daten generiert, wird der Rückruf onLoadFinished(Loader, D)
sofort (innerhalb dieser Funktion) aufgerufen [...].
Das heißt, wenn wir initLoader
nach einer Orientierungsänderung anrufen , erhalten wir sofort einen onLoadFinished
Anruf, da die Daten bereits geladen sind (vorausgesetzt, dies war vor der Änderung der Fall). Das klingt zwar einfach, kann aber schwierig sein (lieben wir nicht alle Android ...).
Wir müssen zwischen zwei Fällen unterscheiden:
- Behandelt die Konfigurationsänderungen selbst: Dies ist der Fall für Fragmente, die setRetainInstance (true) verwenden, oder für eine Aktivität mit dem entsprechenden
android:configChanges
Tag im Manifest. Diese Komponenten erhalten nach einer Bildschirmdrehung keinen onCreate-Aufruf. Denken Sie also daran, eine initLoader/restartLoader
andere Rückrufmethode (z onActivityCreated(Bundle)
. B. in
) aufzurufen
. Um die Loader initialisieren zu können, müssen die Loader-IDs gespeichert werden (z. B. in einer Liste). Da die Komponente bei Konfigurationsänderungen beibehalten wird, können wir einfach die vorhandenen Loader-IDs durchlaufen und aufrufen initLoader(loaderid,
...)
.
- Verarbeitet Konfigurationsänderungen nicht selbst: In diesem Fall können die Loader in onCreate initialisiert werden, aber wir müssen die Loader-IDs manuell beibehalten, sonst können wir die erforderlichen initLoader / restartLoader-Aufrufe nicht ausführen. Wenn die IDs in einer ArrayList gespeichert sind, führen wir einen
outState.putIntegerArrayList(loaderIdsKey, loaderIdsArray)
in onSaveInstanceState durch und stellen die IDs in onCreate: wieder her,
loaderIdsArray =
savedInstanceState.getIntegerArrayList(loaderIdsKey)
bevor wir die initLoader-Aufrufe ausführen.
initLoader
nach einer Rotation verwenden (und alle Rückrufe beendet sind, ist Loader inaktiv), erhalten Sie keinenonLoadFinished
Rückruf, aber wenn Sie verwenden, werdenrestartLoader
Sie?