Les chargeurs sont obsolètes depuis Android 9 (niveau d'API 28). L'option recommandée pour
gérer le chargement des données lors de la gestion des cycles de vie Activity
et Fragment
consiste à utiliser
combinaison d'objets ViewModel
et LiveData
.
Les modèles de vue survivent aux changements de configuration, comme les chargeurs, mais avec
moins de code récurrent. LiveData
permet de charger des données qui tiennent compte du cycle de vie et que vous pouvez réutiliser
plusieurs modèles de vues. Vous pouvez également combiner LiveData
en utilisant
MediatorLiveData
Toutes les requêtes observables, telles que celles provenant d'une
La base de données Room peut être utilisée pour observer les modifications.
aux données.
ViewModel
et LiveData
sont également disponibles si vous n'y avez pas accès
à LoaderManager
, comme dans un
Service
Utiliser les deux dans
Le tandem permet d'accéder facilement aux données dont votre application a besoin sans avoir à gérer l'interface utilisateur.
tout au long du cycle de vie. Pour en savoir plus sur LiveData
, consultez les
Présentation de LiveData
Pour en savoir plus sur
ViewModel
, consultez la présentation de ViewModel
.
L'API Loader vous permet de charger des données à partir d'un
fournisseur de contenu
ou une autre source de données à afficher dans une FragmentActivity
ou Fragment
.
Sans chargeurs, vous pouvez rencontrer certains des problèmes suivants:
- Si vous extrayez les données directement dans l'activité ou le fragment, vos utilisateurs souffrent d'un manque de réactivité dû à des performances potentiellement lentes à partir du thread UI.
- Si vous récupérez les données d'un autre thread, peut-être avec
AsyncTask
, vous êtes alors responsable de la gestion à la fois de ce thread et le thread UI via divers événements de cycle de vie d'activité ou de fragment, tels queonDestroy()
et modifications de configuration.
Les chargeurs permettent de résoudre ces problèmes et présentent d'autres avantages:
- Les chargeurs s'exécutent sur des threads distincts pour éviter que l'interface utilisateur soit lente ou ne répond pas.
- Les chargeurs simplifient la gestion des threads en fournissant des méthodes de rappel lorsque des événements se produisent.
- Les chargeurs persistent et mettent en cache les résultats en cas de modification de la configuration pour empêcher requêtes en double.
- Les chargeurs peuvent implémenter un observateur pour surveiller les modifications apportées aux
source de données. Par exemple,
CursorLoader
automatiquement enregistre unContentObserver
pour déclencher une actualisation lorsque les données changent.
Récapitulatif de l'API Loader
Plusieurs classes et interfaces peuvent être impliquées lors de l'utilisation chargeurs dans une application. Elles sont résumées dans le tableau suivant:
Classe/Interface | Description |
---|---|
LoaderManager |
Une classe abstraite associée à un élément FragmentActivity ou
Fragment pour gérer un ou plusieurs
Loader instances. Il n'y en a qu'une
LoaderManager par activité ou fragment, mais une
LoaderManager peut gérer plusieurs chargeurs.
Pour obtenir un Pour commencer à charger des données à partir d'un chargeur, appelez soit
|
LoaderManager.LoaderCallbacks |
Cette interface contient des méthodes de rappel qui sont appelées lorsque
se produisent. L'interface définit trois méthodes de rappel:
<ph type="x-smartling-placeholder">
initLoader() ou
restartLoader()
|
Loader |
Les chargeurs effectuent le chargement des données. Cette classe est abstraite
comme classe de base pour tous les chargeurs. Vous pouvez directement sous-classer
Loader ou utiliser l'un des outils intégrés suivants
pour simplifier l'implémentation:
<ph type="x-smartling-placeholder">
|
Les sections suivantes vous expliquent comment utiliser ces de classe et d'interface d'une application.
Utiliser des chargeurs dans une application
Cette section explique comment utiliser des chargeurs dans une application Android. Une une application qui utilise des chargeurs inclut généralement les éléments suivants:
FragmentActivity
ouFragment
.- Instance de
LoaderManager
. - Un
CursorLoader
pour charger des données sauvegardées par unContentProvider
. Vous pouvez également implémenter votre propre sous-classe deLoader
ouAsyncTaskLoader
pour charger des données à partir d'une autre source. - Implémentation de
LoaderManager.LoaderCallbacks
. C'est ici que vous créez des chargeurs et que vous gérez vos références de chargeurs. - Un moyen d'afficher les données du chargeur, tel qu'un
SimpleCursorAdapter
. - Une source de données, telle qu'un
ContentProvider
, lorsque vous utilisez unCursorLoader
Démarrer un chargeur
Le LoaderManager
gère une ou plusieurs instances Loader
dans un FragmentActivity
ou
Fragment
Il n'y a qu'un seul élément LoaderManager
par activité ou fragment.
Habituellement,
initialisez un Loader
dans la méthode onCreate()
de l'activité ou dans la méthode
onCreate()
. Toi
procédez comme suit:
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);
La méthode initLoader()
prend
les paramètres suivants:
- Identifiant unique qui identifie le chargeur. Dans cet exemple, l'ID est
0
. - Arguments facultatifs à fournir au chargeur
de construction (
null
dans cet exemple). - Une implémentation
LoaderManager.LoaderCallbacks
, qui les appelsLoaderManager
pour signaler les événements de chargeur. Dans ce exemple, la classe locale implémente l'interfaceLoaderManager.LoaderCallbacks
, qui transmet donc une référence à elle-même,this
.
L'appel initLoader()
garantit qu'un chargeur
est initialisé et actif. Elle a deux résultats possibles:
- Si le chargeur spécifié par l'ID existe déjà, il s'agit du dernier chargeur créé. est réutilisé.
- Si le chargeur spécifié par l'ID n'existe pas,
initLoader()
déclenche MéthodeLoaderManager.LoaderCallbacks
onCreateLoader()
. C'est ici que vous implémentez le code pour instancier et renvoyer un nouveau chargeur. Pour en savoir plus, consultez la section concernantonCreateLoader
.
Dans les deux cas, la valeur LoaderManager.LoaderCallbacks
donnée
est associée au chargeur et est appelée lorsque le
les changements d'état du chargeur. Si, au moment de cet appel, l'appelant est dans son
et que le chargeur demandé existe déjà et a généré son
données, le système appelle onLoadFinished()
immédiatement, pendant initLoader()
. Vous devez vous y préparer. Pour en savoir plus sur ce rappel, consultez la section
onLoadFinished
.
La méthode initLoader()
renvoie le Loader
créé,
mais vous n'avez pas
besoin de capturer une référence à celui-ci. LoaderManager
gère
automatiquement la durée de vie du chargeur. LoaderManager
démarre et arrête le chargement si nécessaire, et maintient l'état du chargeur.
et les contenus associés.
Comme cela l'implique, vous interagissez rarement avec les chargeurs
directement.
Vous utilisez le plus souvent les méthodes LoaderManager.LoaderCallbacks
pour intervenir dans le chargement.
lorsque des événements
particuliers se produisent. Pour en savoir plus à ce sujet, consultez la section Utiliser les rappels LoaderManager.
Redémarrer un chargeur
Lorsque vous utilisez initLoader()
, en tant que
comme indiqué dans la section précédente, elle utilise un chargeur existant avec l'ID spécifié, le cas échéant.
S'il n'y en a pas, il en crée un. Mais vous pouvez parfois vouloir
supprimer vos anciennes données
et recommencez.
Pour supprimer vos anciennes données, utilisez restartLoader()
. Par exemple :
implémentation de SearchView.OnQueryTextListener
redémarrages
le chargeur lorsque la requête
de l'utilisateur change. Le chargeur doit être redémarré pour que
qu'il puisse utiliser le nouveau filtre
de recherche pour effectuer une nouvelle requête.
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; }
Utiliser les rappels LoaderManager
LoaderManager.LoaderCallbacks
est une interface de rappel.
qui permet à un client d'interagir avec LoaderManager
.
Les chargeurs, en particulier CursorLoader
, doivent
de conserver leurs données après leur arrêt. Cela permet aux applications de conserver
des données dans les méthodes onStop()
et onStart()
de l'activité ou du fragment, de sorte que
Lorsque les utilisateurs retournent dans une application, ils n'ont pas à attendre que les données
actualiser la page.
Les méthodes LoaderManager.LoaderCallbacks
vous permettent de savoir quand créer un chargeur et d'indiquer à l'application quand il est
il est temps d'arrêter d'utiliser
les données d'un chargeur.
LoaderManager.LoaderCallbacks
inclut ces éléments
méthodes:
onCreateLoader()
: instancie et renvoie un nouveauLoader
pour l'ID donné.
-
onLoadFinished()
: appelé lorsqu'un chargeur précédemment créé a terminé son chargement.
onLoaderReset()
: appelé lorsqu'un chargeur précédemment créé est réinitialisé, ce qui rend son données indisponibles.
Ces méthodes sont décrites plus en détail dans les sections suivantes.
onCreateLoader
Lorsque vous tentez d'accéder à un chargeur, par exemple via initLoader()
, il vérifie si
le chargeur spécifié par l'ID existe. Si ce n'est pas le cas, elle déclenche la méthode LoaderManager.LoaderCallbacks
onCreateLoader()
. Ce
est l'endroit où vous créez un chargeur. Il s'agit généralement d'une CursorLoader
, mais vous pouvez implémenter votre propre sous-classe Loader
.
Dans l'exemple suivant, onCreateLoader()
de rappel crée un CursorLoader
à l'aide de sa méthode constructeur, qui
nécessite l'ensemble complet des informations nécessaires pour envoyer une requête au ContentProvider
. Plus précisément, il a besoin des éléments suivants:
- uri: URI du contenu à récupérer.
- projection: une liste des colonnes à renvoyer. Réussite
null
renvoie toutes les colonnes, ce qui est inefficace. - selection: un filtre déclarant les lignes à renvoyer.
formaté en tant que clause SQL WHERE (à l'exclusion de WHERE lui-même). Réussite
null
renvoie toutes les lignes pour l'URI donné. - selectionArgs: si vous incluez "?" dans la sélection, sont remplacées par les valeurs de selectionArgs dans l'ordre dans lequel elles apparaissent de la sélection. Les valeurs sont liées sous forme de chaînes.
- sortOrder: comment organiser les lignes au format SQL
la clause ORDER BY (à l'exclusion de la clause ORDER BY elle-même). Réussite :
null
utilise l'ordre de tri par défaut, qui peut être non ordonné.
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
Cette méthode est appelée lorsqu'un chargeur précédemment créé termine son chargement. L'appel de cette méthode est garanti avant la publication des dernières données fourni pour ce chargeur. À ce stade, supprimez toute utilisation les anciennes données, puisqu'elles vont être publiées. Mais ne publiez pas les données vous-même : le chargeur en est le propriétaire et s'en occupe.
Le chargeur libère les données
lorsqu'il sait que l'application n'est plus
l'utilisent. Par exemple, si les données sont un curseur d'un CursorLoader
,
n'appelez pas close()
vous-même. Si le curseur se trouve
placé dans un CursorAdapter
, utilisez la méthode swapCursor()
pour que
l'ancienne Cursor
n'est pas fermée, comme illustré dans l'exemple suivant:
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); }
Réinitialiser onLoader
Cette méthode est appelée lorsqu'un chargeur précédemment créé est en cours de réinitialisation. ce qui rend ses données indisponibles. Ce rappel vous permet de savoir quand les données sont sur le point d'être libérée afin que vous puissiez supprimer votre référence à celui-ci.
Cette implémentation fait appel
swapCursor()
avec la valeur 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); }
Exemple
À titre d'exemple, voici l'implémentation complète d'un Fragment
qui affiche un ListView
contenant
les résultats d'une requête sur le fournisseur
de contenu des contacts. Il utilise un CursorLoader
pour gérer la requête sur le fournisseur.
Comme cet exemple provient d'une application permettant d'accéder aux contacts d'un utilisateur, son
le fichier manifeste doit inclure l'autorisation
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); } }
Autres exemples
Les exemples suivants illustrent l'utilisation des chargeurs:
- <ph type="x-smartling-placeholder"></ph> LoaderCursor: version complète de l'extrait précédent.
- Récupérer une liste de contacts:
tutoriel qui utilise un
CursorLoader
pour récupérer du fournisseur de contacts. - <ph type="x-smartling-placeholder"></ph> LoaderThrottle: exemple d'utilisation de la limitation pour réduire le nombre de requêtes effectuées par un fournisseur de contenu lorsque ses données changent.