Caricatori

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, come onDestroy() 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 un ContentObserver 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 LoaderManager, chiama il numero getSupportLoaderManager() dall'attività o dal frammento.

Per iniziare a caricare i dati da un caricatore, chiama initLoader() o restartLoader(). Il sistema determina automaticamente se un caricatore con lo stesso ID intero è già esiste e crea un nuovo caricatore o riutilizza un caricatore esistente.

LoaderManager.LoaderCallbacks Questa interfaccia contiene metodi di callback che vengono chiamati quando si verificano eventi loader. L'interfaccia definisce tre metodi di callback:
  • onCreateLoader(int, Bundle): quando il sistema richiede la creazione di un nuovo caricatore. Nel codice, crea un oggetto Loader e lo restituisci all'interno del sistema.
  • onLoadFinished(Loader<D>, D): quando un caricatore ha terminato il caricamento dei dati. Di solito mostrare i dati all'utente nel codice.
  • onLoaderReset(Loader<D>): chiamato quando un caricatore creato in precedenza viene reimpostato, quando chiami destroyLoader(int) o quando l'attività o un frammento viene distrutto, rendendo i relativi dati non disponibili. Nel codice, rimuovere eventuali riferimenti ai dati del caricatore.
di Gemini Advanced. L'attività o il frammento in genere implementano questa interfaccia e registrato quando chiami 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:

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 chiamate LoaderManager per segnalare gli eventi di caricamento. In questo Ad esempio, la classe locale implementa l'interfaccia LoaderManager.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 Metodo LoaderManager.LoaderCallbacks onCreateLoader(). È qui che implementi il codice per creare un'istanza e restituire un nuovo caricatore. Per ulteriori discussioni, consulta la sezione su onCreateLoader.

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 ricaricarlo.

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:

  • 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: