Threading Android : tout ce que vous devez savoir

Publié: 2022-03-11

Chaque développeur Android, à un moment ou à un autre, doit gérer les threads dans son application.

Lorsqu'une application est lancée sous Android, elle crée le premier thread d'exécution, dit thread « principal ». Le thread principal est responsable de la distribution des événements aux widgets d'interface utilisateur appropriés ainsi que de la communication avec les composants de la boîte à outils de l'interface utilisateur Android.

Pour que votre application reste réactive, il est essentiel d'éviter d'utiliser le thread principal pour effectuer une opération qui pourrait finir par le bloquer.

Les opérations réseau et les appels de base de données, ainsi que le chargement de certains composants, sont des exemples courants d'opérations à éviter dans le thread principal. Lorsqu'ils sont appelés dans le thread principal, ils sont appelés de manière synchrone, ce qui signifie que l'interface utilisateur restera complètement insensible jusqu'à la fin de l'opération. Pour cette raison, ils sont généralement exécutés dans des threads séparés, ce qui évite ainsi de bloquer l'interface utilisateur pendant leur exécution (c'est-à-dire qu'ils sont exécutés de manière asynchrone à partir de l'interface utilisateur).

Android propose de nombreuses façons de créer et de gérer des threads, et de nombreuses bibliothèques tierces existent qui rendent la gestion des threads beaucoup plus agréable. Cependant, avec autant d'approches différentes à portée de main, choisir la bonne peut être assez déroutant.

Dans cet article, vous découvrirez certains scénarios courants dans le développement Android où le threading devient essentiel et quelques solutions simples qui peuvent être appliquées à ces scénarios et plus encore.

Threading dans Android

Dans Android, vous pouvez classer tous les composants de threading en deux catégories de base :

  1. Threads attachés à une activité/un fragment : ces threads sont liés au cycle de vie de l'activité/du fragment et sont interrompus dès que l'activité/le fragment est détruit.
  2. Threads qui ne sont attachés à aucune activité/fragment : ces threads peuvent continuer à s'exécuter au-delà de la durée de vie de l'activité/du fragment (le cas échéant) à partir duquel ils ont été générés.

Composants de thread qui s'attachent à une activité/un fragment

Tâche asynchrone

AsyncTask est le composant Android le plus basique pour le threading. Il est simple à utiliser et peut être bon pour les scénarios de base.

Exemple d'utilisation :

 public class ExampleActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); new MyTask().execute(url); } private class MyTask extends AsyncTask<String, Void, String> { @Override protected String doInBackground(String... params) { String url = params[0]; return doSomeWork(url); } @Override protected void onPostExecute(String result) { super.onPostExecute(result); // do something with result } } }

AsyncTask , cependant, échoue si vous avez besoin que votre tâche différée s'exécute au-delà de la durée de vie de l'activité/fragment. Il convient de noter que même quelque chose d'aussi simple que la rotation de l'écran peut entraîner la destruction de l'activité.

Chargeurs

Les chargeurs sont la solution au problème mentionné ci-dessus. Les chargeurs peuvent s'arrêter automatiquement lorsque l'activité est détruite et peuvent également redémarrer après la recréation de l'activité.

Il existe principalement deux types de chargeurs : AsyncTaskLoader et CursorLoader . Vous en apprendrez plus sur CursorLoader plus loin dans cet article.

AsyncTaskLoader est similaire à AsyncTask , mais un peu plus compliqué.

Exemple d'utilisation :

 public class ExampleActivity extends Activity{ @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); getLoaderManager().initLoader(1, null, new MyLoaderCallbacks()); } private class MyLoaderCallbacks implements LoaderManager.LoaderCallbacks { @Override public Loader onCreateLoader(int id, Bundle args) { return new MyLoader(ExampleActivity.this); } @Override public void onLoadFinished(Loader loader, Object data) { } @Override public void onLoaderReset(Loader loader) { } } private class MyLoader extends AsyncTaskLoader { public MyLoader(Context context) { super(context); } @Override public Object loadInBackground() { return someWorkToDo(); } } }

Composants de thread qui ne s'attachent pas à une activité/un fragment

Service

Service est un composant utile pour effectuer des opérations longues (ou potentiellement longues) sans aucune interface utilisateur.

Service s'exécute dans le thread principal de son processus d'hébergement ; le service ne crée pas son propre thread et ne s'exécute pas dans un processus séparé, sauf indication contraire.

Exemple d'utilisation :

 public class ExampleService extends Service { @Override public int onStartCommand(Intent intent, int flags, int startId) { doSomeLongProccesingWork(); stopSelf(); return START_NOT_STICKY; } @Nullable @Override public IBinder onBind(Intent intent) { return null; } }

Avec Service , il est de votre responsabilité de l'arrêter lorsque son travail est terminé en appelant soit la stopSelf() soit la méthode stopService() .

Service d'intention

Comme Service , IntentService s'exécute sur un thread séparé et s'arrête automatiquement après avoir terminé son travail.

IntentService est généralement utilisé pour des tâches courtes qui n'ont pas besoin d'être attachées à une interface utilisateur.

Exemple d'utilisation :

 public class ExampleService extends IntentService { public ExampleService() { super("ExampleService"); } @Override protected void onHandleIntent(Intent intent) { doSomeShortWork(); } }

Sept modèles de threading dans Android

Cas d'utilisation n° 1 : effectuer une requête sur le réseau sans nécessiter de réponse du serveur

Parfois, vous souhaiterez peut-être envoyer une requête API à un serveur sans avoir à vous soucier de sa réponse. Par exemple, vous pouvez envoyer un jeton d'enregistrement push au back-end de votre application.

Comme cela implique de faire une demande sur le réseau, vous devez le faire à partir d'un thread autre que le thread principal.

Option 1 : AsyncTask ou chargeurs

Vous pouvez utiliser AsyncTask ou des chargeurs pour passer l'appel, et cela fonctionnera.

Cependant, AsyncTask et les chargeurs dépendent tous deux du cycle de vie de l'activité. Cela signifie que vous devrez soit attendre que l'appel s'exécute et essayer d'empêcher l'utilisateur de quitter l'activité, soit espérer qu'il s'exécutera avant que l'activité ne soit détruite.

Option 2 : Service

Service peut être mieux adapté à ce cas d'utilisation, car il n'est lié à aucune activité. Il pourra donc poursuivre l'appel réseau même après la destruction de l'activité. De plus, puisque la réponse du serveur n'est pas nécessaire, un service ne serait pas non plus limité ici.

Cependant, étant donné qu'un service commencera à s'exécuter sur le thread d'interface utilisateur, vous devrez toujours gérer vous-même le threading. Vous devrez également vous assurer que le service est arrêté une fois l'appel réseau terminé.

Cela exigerait plus d'efforts que ce qui devrait être nécessaire pour une action aussi simple.

Option 3 : IntentService

Ce serait, à mon avis, la meilleure option.

Comme IntentService ne s'attache à aucune activité et qu'il s'exécute sur un thread non-UI, il répond parfaitement à nos besoins ici. De plus, IntentService s'arrête automatiquement, il n'est donc pas nécessaire de le gérer manuellement non plus.

Cas d'utilisation n° 2 : Passer un appel réseau et obtenir la réponse du serveur

Ce cas d'utilisation est probablement un peu plus courant. Par exemple, vous pouvez appeler une API dans le back-end et utiliser sa réponse pour remplir les champs à l'écran.

Option 1 : Service ou IntentService

Bien qu'un Service ou un IntentService se soit bien comporté pour le cas d'utilisation précédent, les utiliser ici ne serait pas une bonne idée. Essayer d'obtenir des données d'un Service ou d'un IntentService dans le thread principal de l'interface utilisateur rendrait les choses très complexes.

Option 2 : AsyncTask ou chargeurs

À première vue, AsyncTask ou les chargeurs semblent être la solution évidente ici. Ils sont faciles à utiliser, simples et directs.

Cependant, lorsque vous utilisez AsyncTask ou des chargeurs, vous remarquerez qu'il est nécessaire d'écrire du code passe-partout. De plus, la gestion des erreurs devient une corvée majeure avec ces composants. Même avec un simple appel de mise en réseau, vous devez être conscient des exceptions potentielles, les détecter et agir en conséquence. Cela nous oblige à envelopper notre réponse dans une classe personnalisée contenant les données, avec des informations d'erreur possibles, et un indicateur indique si l'action a réussi ou non.

C'est beaucoup de travail à faire pour chaque appel. Heureusement, il existe maintenant une solution bien meilleure et plus simple : RxJava.

Option 3 : RxJava

Vous avez peut-être entendu parler de RxJava, la bibliothèque développée par Netflix. C'est presque magique en Java.

RxAndroid vous permet d'utiliser RxJava dans Android et facilite la gestion des tâches asynchrones. Vous pouvez en savoir plus sur RxJava sur Android ici.

RxJava fournit deux composants : Observer et Subscriber .

Un observateur est un composant qui contient une action. Il exécute cette action et renvoie le résultat en cas de réussite ou une erreur en cas d'échec.

Un abonné , quant à lui, est un composant qui peut recevoir le résultat (ou l'erreur) d'un observable, en s'y abonnant.

Avec RxJava, vous créez d'abord un observable :

 Observable.create((ObservableOnSubscribe<Data>) e -> { Data data = mRestApi.getData(); e.onNext(data); })

Une fois l'observable créé, vous pouvez vous y abonner.

Avec la bibliothèque RxAndroid, vous pouvez contrôler le thread dans lequel vous souhaitez exécuter l'action dans l'observable, et le thread dans lequel vous souhaitez obtenir la réponse (c'est-à-dire le résultat ou l'erreur).

Vous enchaînez sur l'observable avec ces deux fonctions :

 .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()

Les planificateurs sont des composants qui exécutent l'action dans un certain thread. AndroidSchedulers.mainThread() est le planificateur associé au thread principal.

Étant donné que notre appel API est mRestApi.getData() et qu'il renvoie un objet Data , l'appel de base peut ressembler à ceci :

 Observable.create((ObservableOnSubscribe<Data>) e -> { try { Data data = mRestApi.getData(); e.onNext(data); } catch (Exception ex) { e.onError(ex); } }) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(match -> Log.i(“rest api, "success"), throwable -> Log.e(“rest api, "error: %s" + throwable.getMessage()));

Sans même entrer dans les autres avantages de l'utilisation de RxJava, vous pouvez déjà voir comment RxJava nous permet d'écrire du code plus mature en éliminant la complexité du threading.

Cas d'usage n°3 : Chaînage des appels réseau

Pour les appels réseau qui doivent être exécutés en séquence (c'est-à-dire où chaque opération dépend de la réponse/du résultat de l'opération précédente), vous devez faire particulièrement attention à la génération de code spaghetti.

Par exemple, vous devrez peut-être effectuer un appel d'API avec un jeton que vous devez d'abord récupérer via un autre appel d'API.

Option 1 : AsyncTask ou chargeurs

L'utilisation AsyncTask ou de chargeurs conduira presque certainement à du code spaghetti. La fonctionnalité globale sera difficile à obtenir correctement et nécessitera une énorme quantité de code passe-partout redondant tout au long de votre projet.

Option 2 : RxJava utilisant flatMap

Dans RxJava, l'opérateur flatMap prend une valeur émise de l'observable source et renvoie un autre observable. Vous pouvez créer un observable, puis créer un autre observable en utilisant la valeur émise par le premier, ce qui les enchaînera essentiellement.

Étape 1. Créez l'observable qui récupère le jeton :

 public Observable<String> getTokenObservable() { return Observable.create(subscriber -> { try { String token = mRestApi.getToken(); subscriber.onNext(token); } catch (IOException e) { subscriber.onError(e); } }); }

Étape 2. Créez l'observable qui obtient les données à l'aide du jeton :

 public Observable<String> getDataObservable(String token) { return Observable.create(subscriber -> { try { Data data = mRestApi.getData(token); subscriber.onNext(data); } catch (IOException e) { subscriber.onError(e); } }); }

Étape 3. Enchaînez les deux observables et abonnez-vous :

 getTokenObservable() .flatMap(new Function<String, Observable<Data>>() { @Override public Observable<Data> apply(String token) throws Exception { return getDataObservable(token); } }) .subscribe(data -> { doSomethingWithData(data) }, error -> handleError(e));

Notez que l'utilisation de cette approche n'est pas limitée aux appels réseau ; il peut fonctionner avec n'importe quel ensemble d'actions qui doivent être exécutées dans une séquence mais sur des threads séparés.

Tous les cas d'utilisation ci-dessus sont assez simples. La commutation entre les threads ne s'est produite qu'après que chacun ait terminé sa tâche. Des scénarios plus avancés, par exemple, où deux threads ou plus doivent communiquer activement entre eux, peuvent également être pris en charge par cette approche.

Cas d'utilisation n° 4 : Communiquer avec le thread d'interface utilisateur à partir d'un autre thread

Considérez un scénario dans lequel vous souhaitez télécharger un fichier et mettre à jour l'interface utilisateur une fois qu'il est terminé.

Étant donné que le téléchargement d'un fichier peut prendre beaucoup de temps, il n'est pas nécessaire de faire attendre l'utilisateur. Vous pouvez utiliser un service, et probablement IntentService , pour implémenter la fonctionnalité ici.

Dans ce cas, cependant, le plus grand défi est de pouvoir invoquer une méthode sur le thread d'interface utilisateur une fois le téléchargement du fichier (qui a été effectué dans un thread séparé) terminé.

Option 1 : RxJava à l'intérieur du service

RxJava, seul ou à l'intérieur d'un IntentService , peut ne pas être idéal. Vous devrez utiliser un mécanisme basé sur le rappel lors de l'abonnement à un Observable , et IntentService est conçu pour effectuer des appels synchrones simples, pas des rappels.

En revanche, avec un Service , vous devrez arrêter manuellement le service, ce qui demande plus de travail.

Option 2 : Récepteur de diffusion

Android fournit ce composant, qui peut écouter des événements globaux (par exemple, des événements de batterie, des événements de réseau, etc.) ainsi que des événements personnalisés. Vous pouvez utiliser ce composant pour créer un événement personnalisé qui est déclenché lorsque le téléchargement est terminé.

Pour ce faire, vous devez créer une classe personnalisée qui étend BroadcastReceiver , l'inscrire dans le manifeste et utiliser Intent et IntentFilter pour créer l'événement personnalisé. Pour déclencher l'événement, vous aurez besoin de la méthode sendBroadcast .

Manifeste:

 <receiver android:name="UploadReceiver"> <intent-filter> <action android:name="com.example.upload"> </action> </intent-filter> </receiver>

Receveur:

 public class UploadReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { if (intent.getBoolean(“success”, false) { Activity activity = (Activity)context; activity.updateUI(); } }

Expéditeur:

 Intent intent = new Intent(); intent.setAction("com.example.upload"); sendBroadcast(intent);

Cette approche est une option viable. Mais comme vous l'avez remarqué, cela implique un certain travail, et trop de diffusions peuvent ralentir les choses.

Option 3 : Utilisation du gestionnaire

Un Handler est un composant qui peut être attaché à un thread, puis amené à effectuer une action sur ce thread via de simples messages ou des tâches Runnable . Il fonctionne en conjonction avec un autre composant, Looper , qui est en charge du traitement des messages dans un thread particulier.

Lorsqu'un Handler est créé, il peut obtenir un objet Looper dans le constructeur, qui indique à quel thread le gestionnaire est attaché. Si vous souhaitez utiliser un gestionnaire attaché au thread principal, vous devez utiliser le looper associé au thread principal en appelant Looper.getMainLooper() .

Dans ce cas, pour mettre à jour l'interface utilisateur à partir d'un thread d'arrière-plan, vous pouvez créer un gestionnaire attaché au thread d'interface utilisateur, puis publier une action en tant que Runnable :

 Handler handler = new Handler(Looper.getMainLooper()); handler.post(new Runnable() { @Override public void run() { // update the ui from here } });

Cette approche est bien meilleure que la première, mais il existe un moyen encore plus simple de le faire…

Option 3 : Utiliser EventBus

EventBus , une bibliothèque populaire de GreenRobot, permet aux composants de communiquer en toute sécurité entre eux. Étant donné que notre cas d'utilisation est celui où nous voulons uniquement mettre à jour l'interface utilisateur, cela peut être le choix le plus simple et le plus sûr.

Étape 1. Créez une classe d'événements. par exemple, UIEvent .

Étape 2. Abonnez-vous à l'événement.

 @Subscribe(threadMode = ThreadMode.MAIN) public void onUIEvent(UIEvent event) {/* Do something */}; register and unregister eventbus : @Override public void onStart() { super.onStart(); EventBus.getDefault().register(this); } @Override public void onStop() { super.onStop(); EventBus.getDefault().unregister(this); }

Étape 3. Publiez l'événement : EventBus.getDefault().post(new UIEvent());

Avec le paramètre ThreadMode dans l'annotation, vous spécifiez le fil sur lequel vous souhaitez vous abonner à cet événement. Dans notre exemple ici, nous choisissons le thread principal, car nous voulons que le récepteur de l'événement puisse mettre à jour l'interface utilisateur.

Vous pouvez structurer votre classe UIEvent pour contenir des informations supplémentaires si nécessaire.

Dans le service:

 class UploadFileService extends IntentService { // … Boolean success = uploadFile(File file); EventBus.getDefault().post(new UIEvent(success)); // ... }

Dans l'activité/fragment :

 @Subscribe(threadMode = ThreadMode.MAIN) public void onUIEvent(UIEvent event) {//show message according to the action success};

En utilisant la EventBus library , la communication entre les threads devient beaucoup plus simple.

Cas d'utilisation n° 5 : communication bidirectionnelle entre les threads en fonction des actions de l'utilisateur

Supposons que vous construisiez un lecteur multimédia et que vous vouliez qu'il puisse continuer à jouer de la musique même lorsque l'écran de l'application est fermé. Dans ce scénario, vous souhaiterez que l'interface utilisateur puisse communiquer avec le fil multimédia (par exemple, lecture, pause et autres actions) et vous souhaiterez également que le fil multimédia mette à jour l'interface utilisateur en fonction de certains événements (par exemple, erreur, état de la mise en mémoire tampon). , etc).

Un exemple de lecteur multimédia complet dépasse le cadre de cet article. Vous pouvez cependant trouver de bons tutoriels ici et ici.

Option 1 : Utiliser EventBus

Vous pouvez utiliser EventBus ici. Cependant, il n'est généralement pas sûr de publier un événement à partir du fil d'interface utilisateur et de le recevoir dans un service. En effet, vous n'avez aucun moyen de savoir si le service est en cours d'exécution lorsque vous avez envoyé le message.

Option 2 : Utiliser BoundService

Un BoundService est un Service lié à une activité/fragment. Cela signifie que l'activité/le fragment sait toujours si le service est en cours d'exécution ou non et, en plus, il a accès aux méthodes publiques du service.

Pour l'implémenter, vous devez créer un Binder personnalisé dans le service et créer une méthode qui renvoie le service.

 public class MediaService extends Service { private final IBinder mBinder = new MediaBinder(); public class MediaBinder extends Binder { MediaService getService() { // Return this instance of LocalService so clients can call public methods return MediaService.this; } } @Override public IBinder onBind(Intent intent) { return mBinder; } }

Pour lier l'activité au service, vous devez implémenter ServiceConnection , qui est la classe surveillant l'état du service, et utiliser la méthode bindService pour établir la liaison :

 // in the activity MediaService mService; // flag indicates the bound status boolean mBound; @Override protected void onStart() { super.onStart(); // Bind to LocalService Intent intent = new Intent(this, MediaService.class); bindService(intent, mConnection, Context.BIND_AUTO_CREATE); } private ServiceConnection mConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName className, IBinder service) { MediaBinder binder = (MediaBinder) service; mService = binder.getService(); mBound = true; } @Override public void onServiceDisconnected(ComponentName arg0) { mBound = false; } };

Vous pouvez trouver un exemple de mise en œuvre complet ici.

Pour communiquer avec le service lorsque l'utilisateur appuie sur le bouton Lecture ou Pause, vous pouvez vous lier au service, puis appeler la méthode publique appropriée sur le service.

Lorsqu'il y a un événement multimédia et que vous souhaitez le communiquer à l'activité/fragment, vous pouvez utiliser l'une des techniques précédentes (par exemple, BroadcastReceiver , Handler ou EventBus ).

Cas d'usage n°6 : Exécuter des actions en parallèle et obtenir des résultats

Supposons que vous construisiez une application touristique et que vous souhaitiez afficher les attractions sur une carte extraite de plusieurs sources (différents fournisseurs de données). Étant donné que toutes les sources ne sont peut-être pas fiables, vous pouvez ignorer celles qui ont échoué et continuer à afficher la carte de toute façon.

Pour paralléliser le processus, chaque appel d'API doit avoir lieu dans un thread différent.

Option 1 : Utiliser RxJava

Dans RxJava, vous pouvez combiner plusieurs observables en un seul à l'aide des opérateurs merge() ou concat() . Vous pouvez alors vous inscrire sur l'observable « fusionné » et attendre tous les résultats.

Cette approche, cependant, ne fonctionnera pas comme prévu. Si un appel d'API échoue, l'observable fusionné signalera un échec global.

Option 2 : Utiliser des composants Java natifs

L' ExecutorService en Java crée un nombre fixe (configurable) de threads et exécute des tâches sur eux simultanément. Le service renvoie un objet Future qui renvoie finalement tous les résultats via la méthode invokeAll() .

Chaque tâche que vous envoyez à ExecutorService doit être contenue dans l'interface Callable , qui est une interface permettant de créer une tâche pouvant lever une exception.

Une fois que vous obtenez les résultats d' invokeAll() , vous pouvez vérifier chaque résultat et procéder en conséquence.

Supposons, par exemple, que vous ayez trois types d'attraction provenant de trois points de terminaison différents et que vous souhaitiez effectuer trois appels parallèles :

 ExecutorService pool = Executors.newFixedThreadPool(3); List<Callable<Object>> tasks = new ArrayList<>(); tasks.add(new Callable<Object>() { @Override public Integer call() throws Exception { return mRest.getAttractionType1(); } }); // ... try { List<Future<Object>> results = pool.invokeAll(tasks); for (Future result : results) { try { Object response = result.get(); if (response instance of AttractionType1... {} if (response instance of AttractionType2... {} ... } catch (ExecutionException e) { e.printStackTrace(); } } } catch (InterruptedException e) { e.printStackTrace(); }

De cette façon, vous exécutez toutes les actions en parallèle. Vous pouvez donc rechercher les erreurs dans chaque action séparément et ignorer les échecs individuels, le cas échéant.

Cette approche est plus facile que d'utiliser RxJava. Il est plus simple, plus court et n'échoue pas à toutes les actions à cause d'une exception.

Cas d'utilisation n° 7 : Interroger la base de données SQLite locale

Lorsqu'il s'agit d'une base de données SQLite locale, il est recommandé d'utiliser la base de données à partir d'un thread d'arrière-plan, car les appels de base de données (en particulier avec des bases de données volumineuses ou des requêtes complexes) peuvent prendre du temps, ce qui entraîne le gel de l'interface utilisateur.

Lorsque vous interrogez des données SQLite, vous obtenez un objet Cursor qui peut ensuite être utilisé pour récupérer les données réelles.

 Cursor cursor = getData(); String name = cursor.getString(<colum_number>);

Option 1 : Utiliser RxJava

Vous pouvez utiliser RxJava et obtenir les données de la base de données, tout comme nous obtenons les données de notre back-end :

 public Observable<Cursor> getLocalDataObservable() { return Observable.create(subscriber -> { Cursor cursor = mDbHandler.getData(); subscriber.onNext(cursor); }); }

Vous pouvez utiliser l'observable renvoyé par getLocalDataObservable() comme suit :

 getLocalDataObservable() .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(cursor -> String name = cursor.getString(0), throwable -> Log.e(“db, "error: %s" + throwable.getMessage()));

Bien qu'il s'agisse certainement d'une bonne approche, il y en a une qui est encore meilleure, car il existe un composant conçu uniquement pour ce scénario.

Option 2 : Utiliser CursorLoader + ContentProvider

Android fournit CursorLoader , un composant natif permettant de charger des données SQLite et de gérer le thread correspondant. C'est un Loader qui renvoie un Cursor , que nous pouvons utiliser pour obtenir les données en appelant des méthodes simples telles que getString() , getLong() , etc.

 public class SimpleCursorLoader extends FragmentActivity implements LoaderManager.LoaderCallbacks<Cursor> { public static final String TAG = SimpleCursorLoader.class.getSimpleName(); private static final int LOADER_ID = 0x01; private TextView textView; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.simple_cursor_loader); textView = (TextView) findViewById(R.id.text_view); getSupportLoaderManager().initLoader(LOADER_ID, null, this); } public Loader<Cursor> onCreateLoader(int i, Bundle bundle) { return new CursorLoader(this, Uri.parse("content://com.github.browep.cursorloader.data") , new String[]{"col1"}, null, null, null); } public void onLoadFinished(Loader<Cursor> cursorLoader, Cursor cursor) { if (cursor != null && cursor.moveToFirst()) { String text = textView.getText().toString(); while (cursor.moveToNext()) { text += "<br />" + cursor.getString(1); cursor.moveToNext(); } textView.setText(Html.fromHtml(text) ); } } public void onLoaderReset(Loader<Cursor> cursorLoader) { } }

CursorLoader fonctionne avec le composant ContentProvider . Ce composant fournit une pléthore de fonctionnalités de base de données en temps réel (par exemple, les notifications de changement, les déclencheurs, etc.) qui permettent aux développeurs de mettre en œuvre une meilleure expérience utilisateur beaucoup plus facilement.

Il n'y a pas de solution miracle au threading dans Android

Android propose de nombreuses façons de gérer et de gérer les threads, mais aucune d'entre elles n'est une solution miracle.

Choisir la bonne approche de threading, en fonction de votre cas d'utilisation, peut faire toute la différence en rendant la solution globale facile à mettre en œuvre et à comprendre. Les composants natifs conviennent bien pour certains cas, mais pas pour tous. Il en va de même pour les solutions tierces fantaisistes.

J'espère que vous trouverez cet article utile lorsque vous travaillerez sur votre prochain projet Android. Partagez avec nous votre expérience de threading dans Android ou tout cas d'utilisation où les solutions ci-dessus fonctionnent bien - ou ne fonctionnent pas, d'ailleurs - dans les commentaires ci-dessous.