Fonctionnement en arrière-plan

Maintenant que nous en savons un peu sur les éléments d'interface utilisateur et les écrans, nous devons les rendre réactifs.

La réactivité n’est pas seulement une question de rapidité: combien de travail pouvez-vous faire dans une période donnée? L’important est la rapidité avec laquelle l’application se sent. Quand les gens disent qu’une application est réactive, ils veulent souvent dire que l’application ne les empêche pas de faire ce qu’ils essaient de faire. Cela ne les gêne pas. Si vous avez déjà utilisé une application qui vient de geler lorsque vous cliquez sur un bouton, vous pouvez comprendre de quoi nous parlons. Cela ne bloque pas.

Pensez à bloquer comme appeler quelqu'un au téléphone. Lorsque vous appelez, vous entendez la sonnerie et vous attendez que l’autre personne décroche. L'appel ne peut pas être lancé à moins que l'autre personne décroche. On peut dire qu'un appel téléphonique est une opération bloquante parce que les choses doivent se dérouler dans l'ordre. Vous composez, le téléphone sonne, l'autre personne le décroche, puis vous parlez. Aucune de ces choses ne peut arriver en même temps. Toutes les étapes impliquent une forme d’attente ou de blocage de la terminologie informatique. Dans ce chapitre, nous examinerons ce qui se passe lorsque certaines tâches prennent beaucoup de temps et ce que nous pouvons faire pour éviter ces problèmes.

Long Running Tasks

Les utilisateurs peuvent parfois tolérer le blocage dans leur vie quotidienne, comme faire la queue pour renouveler des licences ou faire l'épicerie, ou attendre que quelqu'un décroche le téléphone, etc. Mais ils pourraient être moins tolérants lors de l'utilisation de votre application. Même la plate-forme Android ne tolérera pas votre application si elle prend trop de temps à faire quoi que ce soit: le WindowManager et l'ActivityManager d'Android sont les agents de la réactivité. Lorsqu'un utilisateur clique sur un bouton ou interagit avec une vue déclenchant un événement, votre application n'a pas beaucoup de temps pour terminer ce qu'elle est censée faire. en fait, il ne lui reste plus que 5 secondes avant d'être tué par le runtime. Et d’ici là, vous verrez la fameuse erreur ANR (l’application ne répond pas). Pensez-y comme au BSOD d’Android (écran bleu de la mort).Selon les directives Android, une application nécessite entre 100 ms et 200 ms pour effectuer une tâche dans un gestionnaire d’événements - peu de temps, nous devons donc vraiment nous assurer que nous ne faisons rien de trop fou dans un gestionnaire d’événements. Mais c’est plus facile à dire qu’à faire, et il existe quelques scénarios dans lesquels nous ne serons pas en contrôle total de ce que nous faisons dans un gestionnaire d’événements. Nous pouvons en énumérer quelques-uns ici. Lorsque nous lisons un fichier: nos programmes doivent sauvegarder des données ou les lire à un moment donné. L'opération d'entrée-sortie de fichier peut être notoirement imprévisible parfois; vous ne savez tout simplement pas quelle sera la taille de ce fichier. Si elle est trop grande, il vous faudra peut-être plus de 200 ms pour effectuer les tâches.Lorsque nous interagissons avec une base de données - Nous interagissons avec une base de données en lui donnant des commandes pour la lecture, la mise à jour, la création et la suppression de données. Comme pour les fichiers, nous pouvons parfois émettre une commande qui renverra beaucoup de données; cela pourrait nous prendre un certain temps pour traiter ces enregistrementsLorsque nous interagissons avec le réseau - Lorsque nous entrons et sortons des données sur des sockets réseau, nous sommes à la merci de la condition du réseau. Si elle n’est pas congestionnée ou en panne, c’est bon pour nous. Mais ce n’est pas toujours le cas et ce n’est pas toujours rapide; si vous écrivez des codes qui traitent du réseau dans un gestionnaire d'événements, vous courez le risque de l'ANRLorsque nous utilisons le code d’autres personnes - Nous comptons de plus en plus sur des API pour construire nos applications, et cela pour une bonne raison: elles nous font gagner du temps. Mais nous ne pouvons pas toujours savoir comment ces API sont construites et quels types d’opérations elles ont sous le capot (lisez-vous toujours le code source de toutes les API que vous utilisez?)Alors, que devons-nous faire pour que nos applications ne rencontrent pas d’ANR? Nous ne pouvons certainement pas éviter les choses énumérées dans ce qui précède, car la plupart des applications modernes (et utiles) devront faire une ou plusieurs (ou toutes) de ces choses. En fin de compte, la solution consiste à exécuter les tâches en arrière-plan. Il existe deux façons de procéder, mais dans cette section, nous examinerons l’exécution de nos codes dans une tâche asynchrone. 

Démonstration du Projet

Les détails du projet pour ce chapitre sont les suivants

.

Application name

Async

Lieu du projet Utilisez la valeur par défaut
Facteur de forme Téléphone et tablette seulement
SDK minimum API 23 guimauve
Type d'activité Vide
Nom de l'activité MainActivity (par défaut)
Nom de la mise en page activity_main (par défaut)

 Ce projet est destiné à casser et à avoir des problèmes de performance. Lorsque l'utilisateur clique sur «tâche longue», il simule une tâche longue, mais tout ce que nous faisons est de compter de 1 à 15; chaque tick du compte prend 2 secondes. Nous retenons effectivement l'utilisateur en otage pendant au moins 30 secondes, au cours desquelles il ne peut pas faire grand chose d'autre dans l'application.La figure 8-1 montre à quoi notre écran ressemblera, et le Listing 8-1, la définition XML du fichier de présentation. 

Figure 8-1. activity_main (design mode)

 

Listing 8-1. activity_main.xml

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <android.support.constraint.ConstraintLayout
  3. xmlns:android="http://schemas.android.com/apk/res/android"
  4. xmlns:app="http://schemas.android.com/apk/res-auto"
  5. xmlns:tools="http://schemas.android.com/tools"
  6. android:layout_width="match_parent"
  7. android:layout_height="match_parent"
  8. tools:context="com.example.ted.async.MainActivity">
  9. <Button
  10. android:id="@+id/button"
  11. android:layout_width="wrap_content"
  12. android:layout_height="wrap_content"
  13. android:layout_marginBottom="317dp"
  14. android:gravity="center"
  15. android:text="Long running task"
  16. app:layout_constraintBottom_toBottomOf="parent"
  17. app:layout_constraintLeft_toLeftOf="parent"
  18. app:layout_constraintRight_toRightOf="parent"
  19. app:layout_constraintTop_toBottomOf="@+id/textView"/>

Running in the Background

  1. <TextView
  2. android:id="@+id/textView"
  3. android:layout_width="184dp"
  4. android:layout_height="0dp"
  5. android:layout_marginBottom="55dp"
  6. android:layout_marginTop="34dp"
  7. android:gravity="center"
  8. android:text="TextView"
  9. android:textSize="18sp"
  10.  
  11. app:layout_constraintBottom_toTopOf="@+id/button"
  12. app:layout_constraintLeft_toLeftOf="parent"
  13. app:layout_constraintRight_toRightOf="parent"
  14. app:layout_constraintTop_toTopOf="parent"/>
  15. <Button
  16. android:id="@+id/button2"
  17. android:layout_width="wrap_content"
  18. android:layout_height="wrap_content"
  19. android:text="Click"
  20. app:layout_constraintBottom_toBottomOf="parent"
  21. app:layout_constraintLeft_toLeftOf="parent"
  22. app:layout_constraintRight_toRightOf="parent"
  23. app:layout_constraintTop_toTopOf="parent"/>
  24. </android.support.constraint.ConstraintLayout>

Listing 8-2. MainActivity

  1. package com.example.ted.async;
  2. import android.support.v7.app.AppCompatActivity;
  3. import android.os.Bundle;
  4. import android.util.Log;
  5. import android.view.View;
  6. import android.widget.Button;
  7. import android.widget.TextView;
  8.  
  9. public class MainActivity extends AppCompatActivity
  10. implements View.OnClickListener{
  11. private String TAG; TextView tv;
  12. @Override
  13. protected void onCreate(Bundle savedInstanceState) {
  14. super.onCreate(savedInstanceState);
  15. setContentView(R.layout.activity_main);
  16. Button b = (Button) findViewById(R.id.button);
  17. Button b2 = (Button) findViewById(R.id.button2);
  18. tv = (TextView) findViewById(R.id.textView);
  19. TAG = getClass().getSimpleName();
  20. b.setOnClickListener(this);
  21. b2.setOnClickListener(new View.OnClickListener(){
  22. public void onClick(View v) {
  23. Log.i(TAG, "Clicked");
  24. }
  25. });
  26. }
  27. public void onClick(View v) {
  28. int i = 0;
  29. while (i < 15) {
  30. try {
  31.  
  32. Thread.sleep(2000);
  33. tv.setText(String.format("Value of i = %d", i));
  34. Log.i(TAG, String.format("value of i = %d", i++ ));
  35. }
  36. e.printStackTrace();
  37. }
  38. }
  39. }
  40. }

Ce bloc de code est conçu pour simuler une activité fastidieuse à l'intérieur d'un gestionnaire d'événements.

Cela arrêtera l'exécution pendant 10 secondes

Toutes les 10 secondes, nous écrivons la valeur de i dans l'interface utilisateurCe code n’ira pas très loin. Il y aura bientôt une erreur ANR (Figure 8-2) si vous cliquez sur le bouton «tâche longue» puis sur l'autre bouton. Vous remarquerez que vous ne pourrez pas cliquer dessus, car le thread d'interface utilisateur attend la fin de la «tâche longue»: l'interface utilisateur ne répond plus.  

Figure 8-2. ANR error

 

AsyncTask

Dans la section précédente, le problème que nous avons rencontré était que lorsqu'un gestionnaire d'événements fait quelque chose de long, l'interface utilisateur entière se fige et que l'utilisateur ne peut rien faire d'autre: l'utilisateur est bloqué. AsyncTask était destiné à résoudre ce type de problèmes. Il a été conçu pour rendre l'interface utilisateur réactive, même lors d'opérations qui prennent un certain temps. La figure 8-3 décrit le rôle de la tâche asynchrone dans cette solution.

Figure 8-3. AsyncTask and MainActivity

Voici ce qui se passe dans cette approche.

 

1. MainActivity crée un objet AsyncTask (nous créons essentiellement une classe qui étend une AsyncTask)

2. Appelez la méthode execute de la AsyncTask; dans cette méthode, nous allons passer à la AsyncTask les références d'objet de l'élément d'interface utilisateur que nous voulons mettre à jour

3. Il existe différentes méthodes de cycle de vie de la tâche Async, mais le seul rappel obligatoire à remplacer est doInBackground () - nous écrirons ici toutes les opérations volumineuses.

4. À ce stade, AsyncTask créera un fil d’arrière-plan, mais ce fil est transparent pour nous. on s'en fiche, c'est AsyncTask qui va le gérer, pas nous

 Running in the Background

Dans la méthode doInBackground (), nous pouvons appeler périodiquement publishProgress (). Chaque fois que nous le faisons, le moteur d'exécution appelle la méthode onProgressUpdate () de la tâche AsyncTask, et l'opération est effectuée en toute sécurité. C'est à l'intérieur de cette méthode que nous pouvons faire des mises à jour de l'interface utilisateur. 

Note   

Un thread est une séquence d'instructions très semblable à la séquence d'instructions que nous avons écrite dans les méthodes. Toutefois, un thread est exécuté de manière spéciale: il est exécuté en arrière-plan de sorte qu’il ne bloque pas ce qui est exécuté au premier plan (le thread de l’UI). 
C'est la raison pour laquelle nous avons besoin d'écrire des instructions qui prennent beaucoup de temps pour finir en thread.

Révisons le projet AsyncTask. Tout d'abord, nous devons créer une nouvelle classe qui s'étend de AsyncTask. 

Listing 8-3. Worker.java (Shell)

  1. package com.example.ted.async; import android.os.AsyncTask; public class Worker extends AsyncTask<TextView, String, Boolean> {
  2. @Override
  3. protected Boolean doInBackground(TextView... textViews) { ... }
  4. @Override
  5. protected void onProgressUpdate(String... values) { ... }
  6. }

 La AsyncTask est paramétrée; c'est un type générique, nous devons donc lui passer des arguments. Ces paramètres sont <Params, Progress, Result>; voir le tableau 8-1 pour plus d'informations

 

C’est la seule méthode que nous sommes obligés de contourner. C’est là que nous devrions placer la logique du programme, ce qui peut prendre un certain temps.

Utilisez cette méthode pour communiquer les progrès à l'utilisateur

 

Table 8-1. Arguments to the AsyncTask Class

Parameter

Description

1st arg (Params)

Quelles informations voulez-vous transmettre au fil d'arrière-plan? Il s'agit généralement du ou des éléments d'interface utilisateur que vous souhaitez mettre à jour. Lorsque vous appelez execute depuis MainActivity, vous devrez transmettre ce paramètre à AsyncTask. Ce paramètre se rend automatiquement à la méthode doInBackground. Dans notre exemple, il s'agit d'un objet en mode texte. Nous voulons que le thread d'arrière-plan ait accès à cet élément d'interface utilisateur tout en effectuant son travail.

2nd arg (Progress)

Quel type d'informations voulez-vous que le thread d'arrière-plan réponde à la méthode onProgressUpdate afin de pouvoir spécifier le statut d'une opération de longue durée à l'utilisateur? Dans notre cas, nous voulons mettre à jour l'attribut text de la vue texte, il s'agit donc d'un objet String.

3rd param (Result)

Quel type de données voulez-vous utiliser pour spécifier le statut de doInBackground à la fin de la tâche? Dans notre cas, je voulais juste que ça retourne vrai si tout se passait bien, donc le troisième paramètre est un booléen

 

Listing 8-4. Worker Class Shell

  1. public class Worker extends AsyncTask<TextView,String,Boolean> {
  2. private String TAG;
  3. private TextView tv;
  4. @Override
  5. protected Boolean doInBackground(TextView...textViews) {
  6. tv = textViews[0];
  7. TAG = getClass().getSimpleName();
  8. int i = 0;
  9. while (i++ < 15) {
  10. try {
  11. Thread.sleep(2000);
  12. publishProgress(String.format("Value of i = %d", i));
  13. }
  14. e.printStackTrace();
  15. }
  16. }
  17. return true;
  18. }
  19. @Override
  20. protected void onProgressUpdate(String... values) { ... }
  21. }

La vue texte étant déclarée en haut de la classe, nous pouvons y accéder depuis onProgressUpdate; nous ne pouvons pas encore le définir car nous n'obtiendrons une référence d'objet à cette vue lorsque l'appel de doInBackground

Nous pouvons maintenant définir la vue texte. il nous était déjà transmis lorsque MainActivity a appelé la méthode execute (). Le paramètre de cette méthode est un tableau, mais nous savons que nous n'avons transmis qu'un seul objet d'interface utilisateur (la vue texte). Par conséquent, nous n'obtenons que le premier élément du tableau. Nous pouvons maintenant stocker cette référence à la variable TextView (tv) que nous avons hissée dans chaque tick, nous appellerons publishProgress afin qu'il puisse mettre à jour l'interface utilisateur.Ensuite, implémentons la méthode onProgressUpdate.

Listing 8-5. onProgressUpdate

  1. protected void onProgressUpdate(String... values) {
  2.  
  3. tv.setText(values[0]);
  4. Log.i(TAG, String.format(values[0]));
  5. }

Cette méthode interceptera toutes les valeurs que nous avons passées à la méthode publishProgress. Le paramètre de cette méthode est, encore une fois, un tableau. Et comme nous n’avons passé qu’une chaîne, nous n’obtiendrons que le premier élément et définirons sa valeur en tant qu’attribut texte de l’objet Vue texte. Nous avons essentiellement déplacé la tâche fastidieuse dans MainActivity et l’avons placée dans la classe Worker. L'étape suivante consiste à mettre à jour les codes dans MainActivity.

Listing 8-6. MainActivity

  1. package com.example.ted.async;
  2. import android.support.v7.app.AppCompatActivity;
  3. import android.os.Bundle;
  4. import android.util.Log;
  5. import android.view.View;
  6. import android.widget.Button;
  7. import android.widget.TextView;
  8.  
  9. public class MainActivity extends AppCompatActivity
  10. implements View.OnClickListener{
  11.  
  12. private String TAG; TextView tv;
  13. @Override
  14. protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState);
  15. setContentView(R.layout.activity_main);
  16. Button b = (Button) findViewById(R.id.button);
  17. Button b2 = (Button) findViewById(R.id.button2);
  18. tv = (TextView) findViewById(R.id.textView);
  19. TAG = getClass().getSimpleName();
  20. b.setOnClickListener(this);
  21. b2.setOnClickListener(new View.OnClickListener(){
  22. public void onClick(View v) {
  23. Log.i(TAG, "Clicked");
  24. }
  25. });
  26. }
  27. public void onClick(View v) {
  28. Worker worker = new Worker();
  29. worker.execute(tv);
  30. }
  31. }

Le bloc onCreate reste inchangé par rapport à la section précédente. nous venons de définir les gestionnaires d'événements ici

Créez une instance de la classe AsyncTask Worker. Notez que l'exécution en arrière-plan du AsyncTask n'est pas démarré en créant simplement une instance de celle-ci.

La méthode execute lance l'opération en arrière-plan. Dans cette méthode, nous transmettons tout ce que nous voulons mettre à jour à la AsyncTask. Notez que vous pouvez transmettre plusieurs éléments d'interface utilisateur à la méthode exécute, car ils seront transmis sous la forme d'un tableau dans la méthode doInBackground de AsyncTask.

 

Note

AsyncTask n'est pas conçu pour exécuter des opérations très longues, de l'ordre de quelques minutes. En règle générale, la tâche asynchrone n'est utilisée que pour les opérations qui durent quelques secondes. Plus longtemps que cela et WindowManager / ActivityManager peuvent toujours tuer l'application. Pour les opérations de longue durée, vous devez utiliser les services, mais cela dépasse le cadre de ce manuel.