Per rispondere a questa domanda è necessario scavare nel LoaderManager
codice. Mentre la documentazione per LoaderManager non è abbastanza chiara (o non ci sarebbe questa domanda), la documentazione per LoaderManagerImpl, una sottoclasse del LoaderManager astratto, è molto più illuminante.
initLoader
Chiama per inizializzare un determinato ID con un Caricatore. Se a questo ID è già associato un Caricatore, viene lasciato invariato e tutti i callback precedenti vengono sostituiti con quelli appena forniti. Se al momento non esiste un Caricatore per l'ID, ne viene creato e avviato uno nuovo.
Questa funzione dovrebbe essere generalmente utilizzata durante l'inizializzazione di un componente, per garantire che venga creato un Caricatore su cui si basa. Ciò consente di riutilizzare i dati di un Caricatore esistente se ne esiste già uno, quindi ad esempio quando un'attività viene ricreata dopo una modifica della configurazione, non è necessario ricreare i suoi caricatori.
restartLoader
Chiama per ricreare il Caricatore associato a un determinato ID. Se al momento è presente un Caricatore associato a questo ID, questo verrà annullato / arrestato / distrutto come appropriato. Verrà creato un nuovo Caricatore con gli argomenti indicati e i suoi dati ti verranno consegnati una volta disponibili.
[...] Dopo aver chiamato questa funzione, tutti i Caricatori precedenti associati a questo ID saranno considerati non validi e non riceverai ulteriori aggiornamenti dei dati da essi.
Esistono sostanzialmente due casi:
- Il caricatore con ID non esiste: entrambi i metodi creeranno un nuovo caricatore, quindi non vi è alcuna differenza
- Il caricatore con l'id esiste già:
initLoader
sostituirà solo i callback passati come parametro ma non annullerà o arresterà il caricatore. Ciò CursorLoader
significa che il cursore rimane aperto e attivo (se così fosse prima della initLoader
chiamata). `restartLoader, d'altra parte, annullerà, fermerà e distruggerà il caricatore (e chiuderà l'origine dati sottostante come un cursore) e creerà un nuovo caricatore (che creerebbe anche un nuovo cursore e riascolterebbe la query se il caricatore è un cursore cursore).
Ecco il codice semplificato per entrambi i metodi:
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);
Come possiamo vedere nel caso in cui il caricatore non esista (info == null) entrambi i metodi creeranno un nuovo caricatore (info = createAndInstallLoader (...)). Nel caso in cui il caricatore esista già initLoader
sostituisce solo i callback (info.mCallbacks = ...) mentre restartLoader
inattiva il vecchio caricatore (verrà distrutto quando il nuovo caricatore completa il suo lavoro) e ne crea uno nuovo.
Così detto è ora chiaro quando usare initLoader
e quando usare restartLoader
e perché ha senso avere i due metodi.
initLoader
viene utilizzato per garantire l'esistenza di un caricatore inizializzato. Se non esiste nessuno ne viene creato uno nuovo, se ne esiste già uno viene riutilizzato. Usiamo sempre questo metodo A MENO che non sia necessario un nuovo caricatore perché la query da eseguire è cambiata (non i dati sottostanti ma la query effettiva come nell'istruzione SQL per un CursorLoader), nel qual caso chiameremo restartLoader
.
Il ciclo di vita di attività / frammento non ha nulla a che fare con la decisione di utilizzare l'uno o l'altro metodo (e non è necessario tenere traccia delle chiamate usando un flag one-shot come suggerito da Simon)! Questa decisione viene presa esclusivamente sulla base della "necessità" di un nuovo caricatore. Se vogliamo eseguire la stessa query che utilizziamo initLoader
, se vogliamo eseguire una query diversa che utilizziamo restartLoader
.
Potremmo sempre usare, restartLoader
ma sarebbe inefficiente. Dopo una rotazione dello schermo o se l'utente si allontana dall'app e ritorna successivamente alla stessa attività, di solito vogliamo mostrare lo stesso risultato della query e quindi restartLoader
ricreare inutilmente il caricatore e chiudere il risultato della query (potenzialmente costoso) sottostante.
È molto importante comprendere la differenza tra i dati caricati e la "query" per caricare tali dati. Supponiamo di utilizzare un CursorLoader che esegue una query su una tabella per gli ordini. Se un nuovo ordine viene aggiunto a quella tabella, CursorLoader utilizza onContentChanged () per informare l'interfaccia utente per aggiornare e mostrare il nuovo ordine (non è necessario utilizzarlo restartLoader
in questo caso). Se vogliamo visualizzare solo ordini aperti, abbiamo bisogno di una nuova query e utilizzeremo restartLoader
per restituire un nuovo CursorLoader che riflette la nuova query.
C'è qualche relazione tra i due metodi?
Condividono il codice per creare un nuovo Caricatore ma fanno cose diverse quando esiste già un Caricatore.
La chiamata chiama restartLoader
sempre initLoader
?
No, non lo fa mai.
Posso chiamare restartLoader
senza dover chiamare initLoader
?
Sì.
È sicuro chiamare initLoader
due volte per aggiornare i dati?
È sicuro chiamare initLoader
due volte, ma nessun dato verrà aggiornato.
Quando dovrei usare uno dei due e perché ?
Ciò dovrebbe (si spera) essere chiaro dopo le mie spiegazioni sopra.
Modifiche alla configurazione
Un LoaderManager mantiene il suo stato attraverso le modifiche alla configurazione (comprese le modifiche all'orientamento), quindi si potrebbe pensare che non ci sia più niente da fare. Pensa di nuovo...
Prima di tutto, un LoaderManager non mantiene i callback, quindi se non fai nulla non riceverai chiamate ai tuoi metodi di callback simili onLoadFinished()
e simili e molto probabilmente si romperà la tua app.
Pertanto DOVRIAMO chiamare almeno initLoader
per ripristinare i metodi di callback ( restartLoader
ovviamente è anche possibile a). La documentazione afferma:
Se al momento della chiamata il chiamante è nel suo stato di avvio e il caricatore richiesto esiste già e ha generato i suoi dati, il callback onLoadFinished(Loader, D)
verrà chiamato immediatamente (all'interno di questa funzione) [...].
Ciò significa che se chiamiamo initLoader
dopo una modifica dell'orientamento, riceveremo onLoadFinished
immediatamente una chiamata perché i dati sono già caricati (supponendo che fosse il caso prima della modifica). Anche se può sembrare semplice, può essere difficile (non amiamo tutti Android ...).
Dobbiamo distinguere tra due casi:
- Gestisce le modifiche alla configurazione stessa: questo è il caso dei frammenti che usano setRetainInstance (true) o per un'attività con il
android:configChanges
tag corrispondente nel manifest. Questi componenti non riceveranno una chiamata onCreate dopo, ad esempio, una rotazione dello schermo, quindi tieni presente di chiamare
initLoader/restartLoader
un altro metodo di callback (ad es
onActivityCreated(Bundle)
. In). Per poter inizializzare il / i Caricatore / i, è necessario memorizzare gli ID del caricatore (ad es. In un elenco). Poiché il componente viene mantenuto attraverso le modifiche alla configurazione, possiamo semplicemente passare in loop sugli ID caricatore esistenti e chiamare initLoader(loaderid,
...)
.
- Non gestisce da solo le modifiche alla configurazione: in questo caso i caricatori possono essere inizializzati in onCreate ma dobbiamo conservare manualmente gli ID del caricatore o non saremo in grado di effettuare le chiamate initLoader / restartLoader necessarie. Se gli ID sono archiviati in una ArrayList, eseguiremmo un
outState.putIntegerArrayList(loaderIdsKey, loaderIdsArray)
errore in onSaveInstanceState e ripristineremmo gli ID in onCreate:
loaderIdsArray =
savedInstanceState.getIntegerArrayList(loaderIdsKey)
prima di effettuare le chiamate initLoader.
initLoader
(e tutti i callback sono finiti, Loader è inattivo) dopo una rotazione non otterrai unonLoadFinished
callback ma se lo usirestartLoader
lo farai?