Los cargadores dejaron de estar disponibles a partir de Android 9 (nivel de API 28). La opción recomendada para
sobre la carga de datos mientras se controlan los ciclos de vida de Activity
y Fragment
es usar un
combinación de objetos ViewModel
y LiveData
.
Los modelos de vista sobreviven a los cambios de configuración, como los cargadores, pero con
menos código estándar. LiveData
proporciona una forma optimizada para ciclos de vida de cargar datos que puedes volver a usar en
múltiples modelos de vista. También puedes combinar LiveData
con
MediatorLiveData
Cualquier consulta observable, como las de un
Base de datos de Room (se puede usar para observar cambios)
a los datos.
ViewModel
y LiveData
también están disponibles en situaciones en las que no tienes acceso
a LoaderManager
, como en un
Service
Si usas los dos en
proporciona una manera fácil de acceder a los datos que necesita tu app sin tener que lidiar con la IU
en el ciclo de vida del AA. Para obtener más información sobre LiveData
, consulta la
Descripción general de LiveData
. Para obtener más información
ViewModel
, consulta la descripción general de ViewModel
.
La API de Loader permite cargar datos desde un
proveedor de contenido
o alguna otra fuente de datos para mostrar en un FragmentActivity
o Fragment
.
Sin cargadores, algunos de los problemas que puedes encontrar incluyen los siguientes:
- Si recuperas los datos directamente en la actividad o el fragmento, tus usuarios Se trata de una falta de capacidad de respuesta debido a un rendimiento potencialmente lento consultas desde el subproceso de IU.
- Si recuperas los datos de otro subproceso, quizás con
AsyncTask
, serás responsable de administrar el subproceso y el subproceso de IU a través de varios eventos de ciclo de vida de actividades o fragmentos, comoonDestroy()
y cambios de configuración.
Los cargadores resuelven estos problemas y ofrecen otros beneficios:
- Los cargadores se ejecutan en subprocesos separados para evitar una IU lenta o que no responde.
- Los cargadores simplifican la administración de subprocesos, ya que proporcionan métodos de devolución de llamada cuando se producen eventos. de que ocurran cambios.
- Los cargadores persisten y almacenan en caché los resultados entre los cambios de configuración para evitar las consultas duplicadas.
- Los cargadores pueden implementar un observador para supervisar los cambios en la configuración
fuente de datos. Por ejemplo,
CursorLoader
automáticamente registra unContentObserver
para activar una recarga cuando cambian los datos.
Resumen de la API de Loader
Muchas clases e interfaces pueden participar al usar de las cargas de trabajo de una app. Se resumen en la siguiente tabla:
Clase/interfaz | Descripción |
---|---|
LoaderManager |
Una clase abstracta asociada con un FragmentActivity o
Fragment para administrar uno o más
Loader instancias. Solo hay una
LoaderManager por actividad o fragmento, pero un
LoaderManager puede administrar varios cargadores.
Para obtener un Para comenzar a cargar datos desde un cargador, llama a
|
LoaderManager.LoaderCallbacks |
Esta interfaz contiene métodos de devolución de llamada que se llaman cuando
loader. La interfaz define tres métodos de devolución de llamada:
initLoader() o
restartLoader()
|
Loader |
Los cargadores cargan los datos. Esta clase es abstracta y sirve
como la clase base para todos los cargadores. Puedes subclasificar directamente
Loader o usa uno de los siguientes dispositivos integrados
subclases para simplificar la implementación:
|
En las siguientes secciones, se muestra cómo usar estas las clases y las interfaces de una aplicación.
Usa cargadores en una aplicación
Esta sección describe cómo usar cargadores en una aplicación con Android. Los que usa cargadores, por lo general, incluye lo siguiente:
- Una
FragmentActivity
oFragment
. - Una instancia de
LoaderManager
. - Un
CursorLoader
para cargar datos respaldados por unContentProvider
. Como alternativa, puedes implementar tu propia subclase deLoader
oAsyncTaskLoader
para cargar datos desde alguna otra fuente. - Una implementación para
LoaderManager.LoaderCallbacks
Aquí es donde creas cargadores nuevos y administras tus referencias a modelos de carga de datos. - Una forma de mostrar los datos del cargador, como un
SimpleCursorAdapter
. - Una fuente de datos, como un
ContentProvider
, cuando se usa unCursorLoader
Inicia un cargador
LoaderManager
administra una o más instancias de Loader
en un FragmentActivity
o
Fragment
Solo hay un LoaderManager
por actividad o fragmento.
Normalmente,
inicializamos un Loader
dentro del método onCreate()
de la actividad o en el
onCreate()
. Tú
hazlo de la siguiente manera:
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);
El método initLoader()
toma
los siguientes parámetros:
- Un ID único que identifica el cargador. En este ejemplo, el ID es
0
. - Argumentos opcionales para proporcionar al cargador en
construcción (
null
en este ejemplo). - Una implementación de
LoaderManager.LoaderCallbacks
, que Las llamadas aLoaderManager
para informar los eventos del cargador En este ejemplo, la clase local implementa la interfazLoaderManager.LoaderCallbacks
, por lo que pasa una referencia a sí misma,this
.
La llamada a initLoader()
garantiza que un cargador
esté inicializada y activa. Tiene dos resultados posibles:
- Si el cargador especificado por el ID ya existe, el último cargador creado cuando se reutiliza.
- Si el cargador especificado por el ID no existe,
initLoader()
activa laonCreateLoader()
del métodoLoaderManager.LoaderCallbacks
Aquí es donde implementas el código para crear una instancia y mostrar un cargador nuevo. Para obtener más información, consulta la sección sobreonCreateLoader
.
En cualquier caso, el objeto LoaderManager.LoaderCallbacks
especificado
implementación se asocia con el cargador y se llama cuando el
de estado del cargador de carga. Si, en el momento de esta llamada, el emisor se encuentra en su
iniciado y el cargador solicitado ya existe y ha generado su
datos, el sistema llama a onLoadFinished()
de inmediato, durante initLoader()
. Debes estar preparado para que esto suceda. Para obtener más información sobre esta devolución de llamada, consulta la sección sobre .
onLoadFinished
El método initLoader()
muestra el Loader
que se crea.
pero no es necesario
capturar una referencia a él. LoaderManager
administra
la vida del cargador automáticamente. El LoaderManager
inicia y detiene la carga cuando es necesario, y mantiene el estado del cargador
y el contenido asociado.
Esto implica que rara vez interactúas con los cargadores
directamente.
Por lo general, usas los métodos LoaderManager.LoaderCallbacks
para intervenir en la carga.
cuando ocurren eventos particulares. Para obtener más información sobre este tema, consulta la sección Cómo usar las devoluciones de llamada de LoaderManager.
Cómo reiniciar un cargador
Cuando usas initLoader()
,
como se muestra en la sección anterior, usa un cargador existente con el ID especificado, si lo hay.
Si no los hay, lo crea. Pero, a veces, quieres descartar datos antiguos
y volver a empezar.
Para descartar tus datos anteriores, usa restartLoader()
. Por ejemplo, el siguiente
implementación de SearchView.OnQueryTextListener
reinicios
el cargador cuando la consulta del usuario cambia. El cargador debe reiniciarse para que
que puede usar el filtro de búsqueda revisado para hacer una consulta nueva.
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; }
Cómo usar las devoluciones de llamada de LoaderManager
LoaderManager.LoaderCallbacks
es una interfaz de devolución de llamada.
que permite al cliente interactuar con LoaderManager
.
Se espera que los cargadores, especialmente CursorLoader
,
retienen sus datos después de ser detenidos. Esto permite que las aplicaciones mantengan su
datos en los métodos onStop()
y onStart()
de la actividad o el fragmento para que
Cuando los usuarios regresan a una aplicación, no tienen que esperar a que los datos se
volver a cargar la página.
Debes usar los métodos LoaderManager.LoaderCallbacks
para saber cuándo crear un cargador nuevo y para indicarle a la aplicación cuándo está
tiempo para dejar de usar los datos de un cargador.
LoaderManager.LoaderCallbacks
incluye estos
métodos:
onCreateLoader()
: crea una instancia deLoader
y muestra una nueva para el ID determinado.
-
onLoadFinished()
: Se llama cuando termina de cargarse un cargador previamente creado.
onLoaderReset()
: llamado cuando se restablece un cargador previamente creado, lo que hace que su datos no disponibles.
Estos métodos se describen más detalladamente en las secciones siguientes.
onCreateLoader
Cuando intentas acceder a un cargador, por ejemplo, a través de initLoader()
, este comprueba si
existe el cargador especificado por el ID. De lo contrario, activa el método LoaderManager.LoaderCallbacks
onCreateLoader()
. Esta
es donde creas un cargador nuevo. Por lo general, es una CursorLoader
, pero puedes implementar tu propia subclase de Loader
.
En el siguiente ejemplo, onCreateLoader()
método de devolución de llamada crea un CursorLoader
con su método de constructor, que
requiere el conjunto completo de información necesaria para realizar una consulta a ContentProvider
. Específicamente, necesita lo siguiente:
- uri: Es el URI del contenido que se recuperará.
- projection: una lista de qué columnas se deben mostrar. Aprobación
null
muestra todas las columnas, lo cual es ineficiente. - selection: Un filtro que declara qué filas mostrar
formateado como una cláusula WHERE de SQL (excluyendo la propia WHERE). Aprobación
null
muestra todas las filas para el URI dado. - selectionArgs: si incluyes ?s en la selección, se reemplazan por los valores de selectionArgs en el orden en que aparecen en la selección. Los valores están vinculados como cadenas.
- sortOrder: cómo ordenar las filas, con el formato de SQL
ORDER BY (excepto la cláusula ORDER BY). Pasando por
null
usa el orden de clasificación predeterminado, que podría no estar ordenado.
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
Se llama a este método cuando un cargador previamente creado termina su carga. Se garantiza que se llamará a este método antes de liberar los últimos datos que se proporciona para este cargador. En este punto, quita todo uso de los datos antiguos, ya que serán liberados. Pero no liberes los datos ya que el cargador es su propietario y se encarga de eso.
El cargador libera los datos una vez que sabe que la aplicación ya no está
y la usan. Por ejemplo, si los datos son un cursor de una CursorLoader
,
no llames a close()
en ella. Si el cursor se coloca
colocado en un CursorAdapter
, usa el método swapCursor()
para que la
El Cursor
anterior no está cerrado, como se muestra en el siguiente ejemplo:
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
Se llama a este método cuando se restablece un cargador previamente creado. Por lo tanto, lo que hace que sus datos no estén disponibles. Esta devolución de llamada te permite saber cuándo se a punto de liberarse para que puedas quitar la referencia a él.
Esta implementación requiere
swapCursor()
con un valor de 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); }
Ejemplo
A modo de ejemplo, esta es la implementación completa de un Fragment
que muestra un ListView
que contiene
los resultados de una consulta sobre el proveedor de contenido de contactos. Este usa un CursorLoader
para administrar la consulta del proveedor.
Como este ejemplo es de una aplicación para acceder a los contactos de un usuario,
manifiesto debe incluir el permiso
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); } }
Más ejemplos
Los siguientes ejemplos indican cómo usar cargadores:
- LoaderCursor: Es una versión completa del fragmento anterior.
- Cómo obtener una lista de contactos:
una explicación en la que se usa un
CursorLoader
para recuperar datos del proveedor de contactos. - LoaderThrottle: Un ejemplo de cómo usar la limitación para reducir la cantidad de las consultas que realiza un proveedor de contenido cuando cambian sus datos.