I caricatori sono deprecati a partire da Android 9 (livello API 28). L'opzione consigliata per
il caricamento dei dati durante la gestione dei cicli di vita Activity
e Fragment
consiste nell'utilizzare una
combinazione di oggetti ViewModel
e LiveData
.
I modelli di visualizzazione rimangono invariati rispetto alle modifiche di configurazione, come i caricatori, ma con
meno codice boilerplate. LiveData
fornisce un modo sensibile al ciclo di vita per caricare i dati che puoi riutilizzare in
più modelli di visualizzazione. Puoi anche combinare LiveData
utilizzando
MediatorLiveData
Qualsiasi query osservabile, come quella da un
Database delle stanze, può essere utilizzato per osservare le modifiche
ai dati.
ViewModel
e LiveData
sono disponibili anche in situazioni in cui non hai accesso
LoaderManager
, ad esempio in una
Service
Utilizzando i due in
offre un modo semplice per accedere ai dati di cui la tua app ha bisogno senza dover gestire l'UI
durante il ciclo di vita di attività. Per scoprire di più su LiveData
, consulta le
Panoramica di LiveData
. Per scoprire di più su
ViewModel
, consulta la panoramica di ViewModel
.
L'API Loader ti consente di caricare i dati da un
fornitore di contenuti
o un'altra origine dati per la visualizzazione in un FragmentActivity
o Fragment
.
Senza i caricatori, potresti riscontrare alcuni problemi:
- Se recuperi i dati direttamente nell'attività o nel frammento, gli utenti soffrono di mancanza di reattività a causa di prestazioni potenzialmente lente delle query dal thread dell'interfaccia utente.
- Se recuperi i dati da un altro thread, forse con
AsyncTask
, sarai responsabile della gestione sia del thread che del file, e il thread dell'interfaccia utente attraverso vari eventi del ciclo di vita di attività o frammenti, comeonDestroy()
e modifiche alla configurazione.
I caricatori risolvono questi problemi e includono altri vantaggi:
- I caricatori vengono eseguiti su thread separati per evitare una UI lenta o che non risponde.
- I caricatori semplificano la gestione dei thread fornendo metodi di callback quando gli eventi che si verificano.
- I caricatori vengono mantenuti e memorizzano nella cache i risultati tra le modifiche alla configurazione per evitare query duplicate.
- I caricatori possono implementare un osservatore per monitorare le modifiche nell'ambiente
origine dati. Ad esempio,
CursorLoader
automaticamente registra unContentObserver
per attivare un ricaricamento quando i dati cambiano.
Riepilogo API Loader
L’utilizzo di più classi e interfacce potrebbe implicare in un'app. Sono riepilogati nella seguente tabella:
Classe/interfaccia | Descrizione |
---|---|
LoaderManager |
Una classe astratta associata a un FragmentActivity o
Fragment per la gestione di una o più
Loader istanze. Ce n'è solo uno
LoaderManager per attività o frammento, ma una
LoaderManager può gestire più caricatori.
Per ricevere un Per iniziare a caricare i dati da un caricatore, chiama
|
LoaderManager.LoaderCallbacks |
Questa interfaccia contiene metodi di callback che vengono chiamati quando
si verificano eventi loader. L'interfaccia definisce tre metodi di callback:
initLoader() o
restartLoader() .
|
Loader |
I caricatori eseguono il caricamento dei dati. Questa classe è astratta e
come classe base per tutti i caricatori. Puoi creare direttamente una sottoclasse
Loader o usa una delle seguenti funzionalità integrate
per semplificare l'implementazione:
|
Le seguenti sezioni mostrano come utilizzare questi e le interfacce di un'applicazione.
Utilizzo dei caricatori in un'applicazione
Questa sezione descrive come utilizzare i caricatori in un'applicazione Android. Un che utilizza caricatori include in genere quanto segue:
- Un
FragmentActivity
oFragment
. - Un'istanza di
LoaderManager
. - A
CursorLoader
per caricare dati supportati daContentProvider
. In alternativa, puoi implementare la tua sottoclasse diLoader
oAsyncTaskLoader
per caricare i dati da un'altra origine. - Un'implementazione per
LoaderManager.LoaderCallbacks
. Qui puoi creare nuovi caricatori e gestire i riferimenti ai file caricatori. - Un modo per visualizzare i dati del caricatore, ad esempio
SimpleCursorAdapter
. - Un'origine dati, ad esempio
ContentProvider
, quando utilizzi un oggettoCursorLoader
.
Avvia un caricatore
LoaderManager
gestisce una o più istanze Loader
all'interno di un FragmentActivity
o
Fragment
. È presente un solo LoaderManager
per attività o frammento.
Di solito
inizializza un Loader
all'interno del metodo onCreate()
dell'attività o del frammento
onCreate()
. Tu
procedi nel seguente modo:
Kotlin
supportLoaderManager.initLoader(0, null, this)
Java
// Prepare the loader. Either re-connect with an existing one, // or start a new one. getSupportLoaderManager().initLoader(0, null, this);
Il metodo initLoader()
richiede
i seguenti parametri:
- Un ID univoco che identifica il caricatore. In questo esempio, l'ID è
0
. - Argomenti facoltativi da fornire al caricatore in
costruzione (
null
in questo esempio). - Un'implementazione
LoaderManager.LoaderCallbacks
, che le chiamateLoaderManager
per segnalare gli eventi di caricamento. In questo Ad esempio, la classe locale implementa l'interfacciaLoaderManager.LoaderCallbacks
, quindi passa un riferimento a se stesso,this
.
La chiamata initLoader()
garantisce che un caricatore
è inizializzato e attivo. Si possono ottenere due risultati:
- Se il caricatore specificato dall'ID esiste già, viene creato l'ultimo caricatore vengono riutilizzate.
- Se il caricatore specificato dall'ID non esiste,
initLoader()
attiva la MetodoLoaderManager.LoaderCallbacks
onCreateLoader()
. È qui che implementi il codice per creare un'istanza e restituire un nuovo caricatore. Per ulteriori discussioni, consulta la sezione suonCreateLoader
.
In entrambi i casi, l'elemento LoaderManager.LoaderCallbacks
specificato
viene associata al caricatore e viene chiamata quando
modifiche allo stato del caricatore. Se, al momento della chiamata, il chiamante è in
stato avviato e il caricatore richiesto esiste già e ha generato la sua
dati, il sistema chiama onLoadFinished()
immediatamente, durante initLoader()
. Bisogna essere preparati affinché ciò accada. Per ulteriori informazioni su questo callback, consulta la sezione su
onLoadFinished
.
Il metodo initLoader()
restituisce il valore Loader
creato,
senza però dover acquisire un riferimento. L'LoaderManager
gestisce
la durata del caricatore. LoaderManager
avvia e interrompe il caricamento quando necessario e mantiene lo stato del caricatore
e i relativi contenuti.
Come implica ciò, raramente interagisci con i caricatori
strato Add.
Solitamente utilizzi i metodi LoaderManager.LoaderCallbacks
per intervenire nel caricamento
processo quando si verificano particolari eventi. Per ulteriori discussioni su questo argomento, consulta la sezione Utilizzo dei callback LoaderManager.
Riavvia un caricatore
Quando usi initLoader()
, come
mostrato nella sezione precedente, utilizza un caricatore esistente con l'ID specificato, se presente.
In caso contrario, ne crea uno. A volte, però, potresti voler eliminare i dati precedenti
e ricominciare da capo.
Per eliminare i dati precedenti, utilizza restartLoader()
. Ad esempio,
implementazione di SearchView.OnQueryTextListener
riavvii
quando la query dell'utente cambia. Il caricatore deve essere riavviato,
di poter utilizzare il filtro di ricerca rivisto per eseguire una nuova query.
Kotlin
fun onQueryTextChanged(newText: String?): Boolean { // Called when the action bar search text has changed. Update // the search filter and restart the loader to do a new query // with this filter. curFilter = if (newText?.isNotEmpty() == true) newText else null supportLoaderManager.restartLoader(0, null, this) return true }
Java
public boolean onQueryTextChanged(String newText) { // Called when the action bar search text has changed. Update // the search filter, and restart the loader to do a new query // with this filter. curFilter = !TextUtils.isEmpty(newText) ? newText : null; getSupportLoaderManager().restartLoader(0, null, this); return true; }
Utilizzare i callback LoaderManager
LoaderManager.LoaderCallbacks
è un'interfaccia di callback
che consente al client di interagire con LoaderManager
.
I caricatori, in particolare CursorLoader
, dovrebbero
conservano i dati dopo essere stati interrotti. Ciò consente alle applicazioni di mantenere
dati nei metodi onStop()
e onStart()
dell'attività o del frammento, in modo che
quando gli utenti tornano a un'applicazione, non devono aspettare che i dati
Ricarica.
Utilizzi i metodi LoaderManager.LoaderCallbacks
per sapere quando creare un nuovo caricatore e per comunicare all'applicazione quando
per interrompere l'utilizzo dei dati di un caricatore.
LoaderManager.LoaderCallbacks
include questi elementi
metodo:
onCreateLoader()
: crea un'istanza e restituisce un nuovoLoader
per l'ID specificato.
-
onLoadFinished()
: quando un caricatore creato in precedenza ha terminato il caricamento.
onLoaderReset()
: quando un caricatore creato in precedenza viene reimpostato, rendendo così dati non disponibili.
Questi metodi sono descritti in modo più dettagliato nelle sezioni seguenti.
onCreateLoader
Quando tenti di accedere a un caricatore, ad esempio tramite initLoader()
, viene verificato se
il caricatore specificato dall'ID esiste. In caso contrario, viene attivato il metodo LoaderManager.LoaderCallbacks
onCreateLoader()
. Questo
consente di creare un nuovo caricatore. In genere si tratta di una CursorLoader
, ma puoi implementare la tua sottoclasse Loader
.
Nell'esempio seguente, onCreateLoader()
di callback crea un CursorLoader
usando il suo metodo costruttore, che
richiede l'insieme completo delle informazioni necessarie per eseguire una query su ContentProvider
. In particolare, deve avere quanto segue:
- uri: l'URI del contenuto da recuperare.
- projection: un elenco delle colonne da restituire. Superato
null
restituisce tutte le colonne, il che è inefficiente. - selection: un filtro che dichiara quali righe restituire,
formattata come clausola WHERE di SQL (escluso WHERE stesso). Superato
null
restituisce tutte le righe per l'URI specificato. - selectionArgs: se includi i simboli ? nella selezione, questi vengono sostituiti dai valori di selectionArgs nell'ordine in cui appaiono in la selezione. I valori sono legati come stringhe.
- sortOrder: come ordinare le righe, formattate come SQL
Clausola ORDER BY (escluso lo stesso ORDER BY). Superato di
null
utilizza l'ordinamento predefinito, che potrebbe non essere ordinato.
Kotlin
// If non-null, this is the current filter the user has provided. private var curFilter: String? = null ... override fun onCreateLoader(id: Int, args: Bundle?): Loader<Cursor> { // This is called when a new Loader needs to be created. This // sample only has one Loader, so we don't care about the ID. // First, pick the base URI to use depending on whether we are // currently filtering. val baseUri: Uri = if (curFilter != null) { Uri.withAppendedPath(ContactsContract.Contacts.CONTENT_URI, Uri.encode(curFilter)) } else { ContactsContract.Contacts.CONTENT_URI } // Now create and return a CursorLoader that will take care of // creating a Cursor for the data being displayed. val select: String = "((${Contacts.DISPLAY_NAME} NOTNULL) AND (" + "${Contacts.HAS_PHONE_NUMBER}=1) AND (" + "${Contacts.DISPLAY_NAME} != ''))" return (activity as? Context)?.let { context -> CursorLoader( context, baseUri, CONTACTS_SUMMARY_PROJECTION, select, null, "${Contacts.DISPLAY_NAME} COLLATE LOCALIZED ASC" ) } ?: throw Exception("Activity cannot be null") }
Java
// If non-null, this is the current filter the user has provided. String curFilter; ... public Loader<Cursor> onCreateLoader(int id, Bundle args) { // This is called when a new Loader needs to be created. This // sample only has one Loader, so we don't care about the ID. // First, pick the base URI to use depending on whether we are // currently filtering. Uri baseUri; if (curFilter != null) { baseUri = Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI, Uri.encode(curFilter)); } else { baseUri = Contacts.CONTENT_URI; } // Now create and return a CursorLoader that will take care of // creating a Cursor for the data being displayed. String select = "((" + Contacts.DISPLAY_NAME + " NOTNULL) AND (" + Contacts.HAS_PHONE_NUMBER + "=1) AND (" + Contacts.DISPLAY_NAME + " != '' ))"; return new CursorLoader(getActivity(), baseUri, CONTACTS_SUMMARY_PROJECTION, select, null, Contacts.DISPLAY_NAME + " COLLATE LOCALIZED ASC"); }
onLoadFinished
Questo metodo viene chiamato quando un caricatore creato in precedenza termina il caricamento. È garantito che questo metodo venga chiamato prima del rilascio degli ultimi dati fornito per questo caricatore. A questo punto, rimuovi ogni uso con i vecchi dati, poiché verranno pubblicati. Ma non divulgare i dati te stesso, il caricatore è di sua proprietà e se ne occupa.
Il caricatore rilascia i dati quando sa che l'applicazione non è più
utilizzandolo. Ad esempio, se i dati sono un cursore da un CursorLoader
,
non chiamare close()
tu. Se il cursore viene posizionato
inserito in un CursorAdapter
, usa il metodo swapCursor()
in modo che
la versione precedente di Cursor
non è chiusa, come illustrato nell'esempio seguente:
Kotlin
private lateinit var adapter: SimpleCursorAdapter ... override fun onLoadFinished(loader: Loader<Cursor>, data: Cursor?) { // Swap the new cursor in. (The framework will take care of closing the // old cursor once we return.) adapter.swapCursor(data) }
Java
// This is the Adapter being used to display the list's data. SimpleCursorAdapter adapter; ... public void onLoadFinished(Loader<Cursor> loader, Cursor data) { // Swap the new cursor in. (The framework will take care of closing the // old cursor once we return.) adapter.swapCursor(data); }
OnLoaderReset
Questo metodo viene chiamato quando un caricatore creato in precedenza viene reimpostato, quindi rendendo non disponibili i suoi dati. Questo callback ti consente di sapere quando i dati vengono che sta per essere rilasciato, così puoi rimuovere il tuo riferimento.
Questa implementazione chiama
swapCursor()
con il valore null
:
Kotlin
private lateinit var adapter: SimpleCursorAdapter ... override fun onLoaderReset(loader: Loader<Cursor>) { // This is called when the last Cursor provided to onLoadFinished() // above is about to be closed. We need to make sure we are no // longer using it. adapter.swapCursor(null) }
Java
// This is the Adapter being used to display the list's data. SimpleCursorAdapter adapter; ... public void onLoaderReset(Loader<Cursor> loader) { // This is called when the last Cursor provided to onLoadFinished() // above is about to be closed. We need to make sure we are no // longer using it. adapter.swapCursor(null); }
Esempio
Ad esempio, ecco l'implementazione completa di un elemento Fragment
che mostra un elemento ListView
contenente
i risultati di una query al fornitore di contenuti dei contatti. Utilizza un CursorLoader
per gestire la query sul provider.
Poiché questo esempio riguarda un'applicazione per accedere ai contatti di un utente,
il file manifest deve includere l'autorizzazione
READ_CONTACTS
.
Kotlin
private val CONTACTS_SUMMARY_PROJECTION: Array<String> = arrayOf( Contacts._ID, Contacts.DISPLAY_NAME, Contacts.CONTACT_STATUS, Contacts.CONTACT_PRESENCE, Contacts.PHOTO_ID, Contacts.LOOKUP_KEY ) class CursorLoaderListFragment : ListFragment(), SearchView.OnQueryTextListener, LoaderManager.LoaderCallbacks<Cursor> { // This is the Adapter being used to display the list's data. private lateinit var mAdapter: SimpleCursorAdapter // If non-null, this is the current filter the user has provided. private var curFilter: String? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // Prepare the loader. Either re-connect with an existing one, // or start a new one. loaderManager.initLoader(0, null, this) } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) // Give some text to display if there is no data. In a real // application, this would come from a resource. setEmptyText("No phone numbers") // We have a menu item to show in action bar. setHasOptionsMenu(true) // Create an empty adapter we will use to display the loaded data. mAdapter = SimpleCursorAdapter(activity, android.R.layout.simple_list_item_2, null, arrayOf(Contacts.DISPLAY_NAME, Contacts.CONTACT_STATUS), intArrayOf(android.R.id.text1, android.R.id.text2), 0 ) listAdapter = mAdapter } override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { // Place an action bar item for searching. menu.add("Search").apply { setIcon(android.R.drawable.ic_menu_search) setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM) actionView = SearchView(activity).apply { setOnQueryTextListener(this@CursorLoaderListFragment) } } } override fun onQueryTextChange(newText: String?): Boolean { // Called when the action bar search text has changed. Update // the search filter, and restart the loader to do a new query // with this filter. curFilter = if (newText?.isNotEmpty() == true) newText else null loaderManager.restartLoader(0, null, this) return true } override fun onQueryTextSubmit(query: String): Boolean { // Don't care about this. return true } override fun onListItemClick(l: ListView, v: View, position: Int, id: Long) { // Insert desired behavior here. Log.i("FragmentComplexList", "Item clicked: $id") } override fun onCreateLoader(id: Int, args: Bundle?): Loader<Cursor> { // This is called when a new Loader needs to be created. This // sample only has one Loader, so we don't care about the ID. // First, pick the base URI to use depending on whether we are // currently filtering. val baseUri: Uri = if (curFilter != null) { Uri.withAppendedPath(Contacts.CONTENT_URI, Uri.encode(curFilter)) } else { Contacts.CONTENT_URI } // Now create and return a CursorLoader that will take care of // creating a Cursor for the data being displayed. val select: String = "((${Contacts.DISPLAY_NAME} NOTNULL) AND (" + "${Contacts.HAS_PHONE_NUMBER}=1) AND (" + "${Contacts.DISPLAY_NAME} != ''))" return (activity as? Context)?.let { context -> CursorLoader( context, baseUri, CONTACTS_SUMMARY_PROJECTION, select, null, "${Contacts.DISPLAY_NAME} COLLATE LOCALIZED ASC" ) } ?: throw Exception("Activity cannot be null") } override fun onLoadFinished(loader: Loader<Cursor>, data: Cursor) { // Swap the new cursor in. (The framework will take care of closing the // old cursor once we return.) mAdapter.swapCursor(data) } override fun onLoaderReset(loader: Loader<Cursor>) { // This is called when the last Cursor provided to onLoadFinished() // above is about to be closed. We need to make sure we are no // longer using it. mAdapter.swapCursor(null) } }
Java
public static class CursorLoaderListFragment extends ListFragment implements OnQueryTextListener, LoaderManager.LoaderCallbacks<Cursor> { // This is the Adapter being used to display the list's data. SimpleCursorAdapter mAdapter; // If non-null, this is the current filter the user has provided. String curFilter; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Prepare the loader. Either re-connect with an existing one, // or start a new one. getLoaderManager().initLoader(0, null, this); } @Override public void onViewCreated(@NonNull View view, Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); // Give some text to display if there is no data. In a real // application, this would come from a resource. setEmptyText("No phone numbers"); // We have a menu item to show in action bar. setHasOptionsMenu(true); // Create an empty adapter we will use to display the loaded data. mAdapter = new SimpleCursorAdapter(getActivity(), android.R.layout.simple_list_item_2, null, new String[] { Contacts.DISPLAY_NAME, Contacts.CONTACT_STATUS }, new int[] { android.R.id.text1, android.R.id.text2 }, 0); setListAdapter(mAdapter); } @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { // Place an action bar item for searching. MenuItem item = menu.add("Search"); item.setIcon(android.R.drawable.ic_menu_search); item.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM); SearchView sv = new SearchView(getActivity()); sv.setOnQueryTextListener(this); item.setActionView(sv); } public boolean onQueryTextChange(String newText) { // Called when the action bar search text has changed. Update // the search filter, and restart the loader to do a new query // with this filter. curFilter = !TextUtils.isEmpty(newText) ? newText : null; getLoaderManager().restartLoader(0, null, this); return true; } @Override public boolean onQueryTextSubmit(String query) { // Don't care about this. return true; } @Override public void onListItemClick(ListView l, View v, int position, long id) { // Insert desired behavior here. Log.i("FragmentComplexList", "Item clicked: " + id); } // These are the Contacts rows that we will retrieve. static final String[] CONTACTS_SUMMARY_PROJECTION = new String[] { Contacts._ID, Contacts.DISPLAY_NAME, Contacts.CONTACT_STATUS, Contacts.CONTACT_PRESENCE, Contacts.PHOTO_ID, Contacts.LOOKUP_KEY, }; public Loader<Cursor> onCreateLoader(int id, Bundle args) { // This is called when a new Loader needs to be created. This // sample only has one Loader, so we don't care about the ID. // First, pick the base URI to use depending on whether we are // currently filtering. Uri baseUri; if (curFilter != null) { baseUri = Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI, Uri.encode(curFilter)); } else { baseUri = Contacts.CONTENT_URI; } // Now create and return a CursorLoader that will take care of // creating a Cursor for the data being displayed. String select = "((" + Contacts.DISPLAY_NAME + " NOTNULL) AND (" + Contacts.HAS_PHONE_NUMBER + "=1) AND (" + Contacts.DISPLAY_NAME + " != '' ))"; return new CursorLoader(getActivity(), baseUri, CONTACTS_SUMMARY_PROJECTION, select, null, Contacts.DISPLAY_NAME + " COLLATE LOCALIZED ASC"); } public void onLoadFinished(Loader<Cursor> loader, Cursor data) { // Swap the new cursor in. (The framework will take care of closing the // old cursor once we return.) mAdapter.swapCursor(data); } public void onLoaderReset(Loader<Cursor> loader) { // This is called when the last Cursor provided to onLoadFinished() // above is about to be closed. We need to make sure we are no // longer using it. mAdapter.swapCursor(null); } }
Altri esempi
I seguenti esempi illustrano come utilizzare i caricatori:
- LoaderCursor: una versione completa dello snippet precedente.
- Recuperare un elenco di contatti:
una procedura dettagliata che utilizza
CursorLoader
per recuperare dati dal fornitore di contatti. - LoaderThrottle. Esempio di come utilizzare la limitazione per ridurre il numero delle query eseguite da un fornitore di contenuti quando i suoi dati cambiano.