Concurrence pratique sur Android avec HaMeR

Dans Comprendre la concurrence sur Android avec HaMeR, nous avons abordé les principes de base de HaMeR (Gestionnaire, Message, et Runnable) cadre. Nous avons couvert ses options, ainsi que quand et comment l'utiliser. 

Aujourd'hui, nous allons créer une application simple pour explorer les concepts appris. Avec une approche pratique, nous verrons comment appliquer les différentes possibilités de HaMeR dans la gestion de la simultanéité sur Android.

1. Exemple d'application

Mettons-nous au travail et publions des Runnable et envoyer Message objets sur un exemple d'application. Pour que ce soit aussi simple que possible, nous n'explorerons que les parties les plus intéressantes. Tous les fichiers de ressources et les appels d'activité standard seront ignorés ici. Je vous conseille donc fortement de consulter le code source de l'exemple d'application avec ses commentaires détaillés.. 

L'application sera composée de:

  • Deux activités, une pour Runnable un autre pour Message appels
  • Deux HandlerThread objets:
    • Travailleur recevoir et traiter les appels de l'interface utilisateur
    • Contre-fil recevoir Message les appels du Travailleur
  • Certaines classes d’utilitaires (pour conserver les objets lors des modifications de configuration et pour la présentation)

2. Enregistrement et réception de runnables

Commençons à expérimenter avec le Handler.post (Runnable) méthode et ses variations, qui ajoutent un runnable à un MessageQueue associé à un fil. Nous allons créer une activité appelée RunnableActivity, qui communique avec un fil d’arrière-plan appelé Travailleur.

le RunnableActivity instancie un fil de fond appelé Travailleur, en passant un Gestionnaire et un WorkerThread.Callback comme paramètres. L'activité peut faire des appels sur Travailleur pour télécharger de manière asynchrone un bitmap et afficher un toast à un moment donné. Les résultats des tâches effectuées par le thread de travail sont transmis à RunnableActivity par les coureurs postés sur le Gestionnaire reçu par Travailleur.

2.1 Préparation d'un gestionnaire pour RunnableActivity

Sur le RunnableActivity nous allons créer un Gestionnaire être passé à Travailleur. le uiHandler sera associé à la Looper depuis le fil de l'interface utilisateur, puisqu'il est appelé depuis ce fil.

Classe publique RunnableActivity étend Activity // Gestionnaire qui permet la communication entre // le WorkerThread et le gestionnaire de tâches protégé. uiHandler; @Override protected void onCreate (Bundle savedInstanceState) super.onCreate (savedInstanceState); setContentView (R.layout.activity_main); // prépare le gestionnaire d'interface utilisateur à envoyer à WorkerThread uiHandler = new Handler (); 

2.2 Déclarer Travailleur et son interface de rappel

le Travailleur est un fil d’arrière-plan où nous allons commencer différents types de tâches. Il communique avec l'interface utilisateur à l'aide du responseHandler et une interface de rappel reçue pendant son instanciation. Les références reçues des activités sont Faible Référence <> type, puisqu’une activité peut être détruite et la référence perdue.

La classe offre une interface pouvant être implémentée par l'interface utilisateur. Il s'étend aussi HandlerThread, une classe d'assistance construite sur Fil qui contient déjà un Looper, et un MessageQueue. Par conséquent, il a le bon installerutiliser le framework HaMeR.

Classe publique WorkerThread étend HandlerThread / ** * Interface pour faciliter les appels sur l'interface utilisateur. * / interface publique Callback void loadImage (image bitmap); void showToast (String msg);  // Ce gestionnaire sera uniquement responsable // de la publication de Runnables sur ce gestionnaire de thread privé postHandler; // Le gestionnaire est reçu de MessageActivity et de RunnableActivity // chargés de recevoir les appels Runnable qui seront traités // sur l'interface utilisateur. Le rappel aidera ce processus. WeakReference privé responseHandler; // Rappel depuis l'interface utilisateur // il s'agit d'un WeakReference car il peut être invalidé // lors de "modifications de configuration" et d'autres événements privés WeakReference rappeler; final final String imageAUrl = "https://pixabay.com/static/uploads/photo/2016/08/05/18/28/mobile-phone-1572901_960_720.jpg"; / ** * Le constructeur reçoit un gestionnaire et un rappel de l'interface utilisateur * * @param responseHandler chargé de publier le Runnable sur l'interface utilisateur * Le rappel @param fonctionne avec le responseHandler * permettant des appels directement sur l'interface utilisateur * / public WorkerThread (gestionnaire responseHandler, callback callback) super (TAG); this.responseHandler = new WeakReference <> (responseHandler); this.callback = new WeakReference <> (rappel); 

2.3 Initialisation Travailleur

Nous devons ajouter une méthode à Travailleur être appelé par les activités qui préparent le fil postHandler pour utilisation. La méthode doit être appelée uniquement après le démarrage du thread..

Classe publique WorkerThread étend HandlerThread / ** * Préparez le postHandler. * Il doit être appelé après le démarrage du thread * / public void prepareHandler () postHandler = new Handler (getLooper ()); 

Sur le RunnableActivity nous devons mettre en œuvre WorkerThread.Callback et initialiser le fil pour qu'il puisse être utilisé.

Classe publique RunnableActivity et Activity met en œuvre WorkerThread.Callback // BackgroundThread responsable du téléchargement de l'image protégée WorkerThread workerThread; / ** * Initialisez l'instance @link WorkerThread * uniquement si elle n'a pas encore été initialisée. * / public void initWorkerThread () if (workerThread == null) workerThread = new WorkerThread (uiHandler, this); workerThread.start (); workerThread.prepareHandler ();  / ** * définit l'image téléchargée sur le fil de discussion sur l'imageView * / @Override public void loadImage (image bitmap) myImage.setImageBitmap (image);  @Override public void showToast (chaîne finale msg) // à implémenter

2.4 Utilisation Handler.post () sur le WorkerThread

le WorkerThread.downloadWithRunnable () la méthode télécharge une bitmap et l'envoie à RunnableActivity à afficher dans une image Voir. Il illustre deux utilisations fondamentales du Handler.post (runnable run) commander:

  • Pour autoriser un thread à publier un objet Runnable dans un MessageQueue associé à lui-même lorsque .poster() est appelé sur un gestionnaire associé au fileur du fil.
  • Pour permettre la communication avec d’autres Threads, lorsque .poster() est appelé sur un gestionnaire associé à un autre utilisateur du fil d'exécution.
  1. le WorkerThread.downloadWithRunnable () méthode affiche un Runnable au Travailleurde MessageQueue en utilisant le postHandler, une Gestionnaire associé à Fil de travailde Looper.
  2. Lorsque le fichier exécutable est traité, il télécharge une Bitmap sur le Travailleur.
  3. Une fois le bitmap téléchargé, le responseHandler, un gestionnaire associé au thread d'interface utilisateur, est utilisé pour poster un exécutable sur le RunnableActivity contenant le bitmap.
  4. Le runnable est traité et le WorkerThread.Callback.loadImage est utilisé pour afficher l'image téléchargée sur un ImageView.
Classe publique WorkerThread étend HandlerThread / ** * publie un fichier exécutable sur WorkerThread *. Télécharge une image et envoie l'image * à l'interface utilisateur @link RunnableActivity * à l'aide de @link #responseHandler avec * aide à partir de @link #callback * / public void downloadWithRunnable () // post Runnable to WorkerThread postHandler.post (new Runnable () @Override public void run () try // dort pendant 2 secondes pour émuler une longue opération, TimeUnit.SECONDS .sleep (2); // Télécharger l'image et l'envoyer à l'interface utilisateur downloadImage (imageAUrl); catch (InterruptedException e) e.printStackTrace (););  / ** * Téléchargez un bitmap en utilisant son URL et * envoyez à l'interface utilisateur l'image téléchargée * / private void downloadImage (String urlStr) // Créez une connexion HttpURLConnection connection = null; try URL url = new URL (urlStr); connexion = (HttpURLConnection) url.openConnection (); // récupère le flux depuis l'URL InputStream in = new BufferedInputStream (connection.getInputStream ()); final Bitmap bitmap = BitmapFactory.decodeStream (in); if (bitmap! = null) // envoie le bitmap téléchargé et un retour à l'interface utilisateur loadImageOnUI (bitmap);  else  catch (IOException e) e.printStackTrace ();  finally if (connection! = null) connection.disconnect ();  / ** * envoie un bitmap à l'interface utilisateur * en envoyant un fichier Runnable à @link #responseHandler * et à l'aide de @link Callback * / private void loadImageOnUI (image bitmap finale) Log.d (TAG, "loadImageOnUI (" + image + ")"); if (checkResponse ()) responseHandler.get (). post (new Runnable () @Override public void run () callback.get (). loadImage (image););  // vérifier si responseHandler est disponible // sinon l'activité passe par un événement de destruction privé boolean checkResponse () return responseHandler.get ()! = null; 

2.5 Utilisation Handler.postAtTime () et Activity.runOnUiThread ()

le WorkerThread.toastAtTime ()programme une tâche à exécuter à un moment donné, présentant une Pain grillé à l'utilisateur. La méthode illustre l’utilisation de la Handler.postAtTime () et le Activity.runOnUiThread ().

  • Handler.postAtTime (exécution, long temps d’utilisation maximal, millis) publie un runnable à un moment donné.
  • Activity.runOnUiThread (exécution) utilise le gestionnaire d'interface utilisateur par défaut pour publier un exécutable sur le thread principal.
Classe publique WorkerThread étend HandlerThread / ** * affiche un Toast sur l'interface utilisateur. * planifie la tâche en tenant compte de l'heure actuelle. * Il peut être programmé à tout moment, * nous utilisons 5 secondes pour faciliter le débogage * / public void toastAtTime () Log.d (TAG, "toastAtTime (): current -" + Calendar.getInstance (). ToString ()); // secondes pour ajouter l'heure actuelle int delaySeconds = 5; // test à l'aide d'une date réelle Calendar scheduleDate = Calendar.getInstance (); // définissant une date future en tenant compte du délai en secondes, définissons // nous utilisons cette approche uniquement pour faciliter les tests. // cela pourrait être fait en utilisant une date définie par l'utilisateur, également une date programmée également leDate.set (dateDate.get (Calendar.YEAR), dateDate.get (Calendar.MONTH), dateDate.get (Calendar.DAY_OF_MONTH), dateDate.get (Calendar.HOUR_OF_DAY ), scheduleDate.get (Calendar.MINUTE), scheduleDate.get (Calendar.SECOND) + delaySeconds); Log.d (TAG, "toastAtTime (): planification à -" + scheduleDate.toString ()); longue prévue = CalculateUptimeMillis (scheduleDate); // publication de Runnable à une heure précise postHandler.postAtTime (new Runnable () @Override public void run () if (callback! = null) callback.get (). showToast ("Toast appelé à l'aide de 'postAtTime ()'. ");, programmé);  / ** * Calcule le @link SystemClock # uptimeMillis () en * une date donnée du calendrier. * / private long CalculateUptimeMillis (Calendrier) long time = calendar.getTimeInMillis (); long currentTime = Calendar.getInstance (). getTimeInMillis (); long diff = time - currentTime; return SystemClock.uptimeMillis () + diff; 
Classe publique RunnableActivity prolongs Activity implémente WorkerThread.Callback / ** * Rappel de @link WorkerThread * Utilise @link #runOnUiThread (Runnable) pour illustrer * cette méthode * / @Override public void showToast (chaîne finale msg)  Log.d (TAG, "showToast (" + msg + ")"); runOnUiThread (new Runnable () @Override public void run () Toast.makeText (getApplicationContext (), msg, Toast.LENGTH_LONG) .show ();); 

3. Envoi de messages avec le MessageActivité Et Travailleur

Ensuite, explorons différentes manières d’utiliser MessageActivité  envoyer et traiter Message objets. le MessageActivité instancie Travailleur, en passant un Gestionnaire en paramètre. le Travailleur a des méthodes publiques avec des tâches à appeler par l'activité pour télécharger un bitmap, télécharger un bitmap aléatoire ou afficher une Pain grillé après un certain délai. Les résultats de toutes ces opérations sont renvoyés à MessageActivité en utilisant Message objets envoyés par le responseHandler.

3.1 Préparation du gestionnaire de réponses MessageActivité

Comme dans le RunnableActivity, dans le MessageActivité nous devrons instancier et initialiser un Travailleur envoyer un Gestionnaire recevoir des données du fil de fond. Cependant, cette fois, nous ne mettrons pas en œuvre WorkerThread.Callback; au lieu de cela, nous recevrons les réponses du Travailleur exclusivement par Message objets.

Depuis la plupart des MessageActivité et RunnableActivity le code est fondamentalement le même, nous allons nous concentrer uniquement sur la uiHandler préparation, qui sera envoyé à Travailleur recevoir des messages de celui-ci.

Premièrement, donnons quelques int clés à utiliser comme identificateurs pour les objets Message.

Classe publique MessageActivity. Activity // Identificateur de message utilisé dans le champ Message.what () public static final int KEY_MSG_IMAGE = 2; public static final int KEY_MSG_PROGRESS = 3; public static final int KEY_MSG_TOAST = 4; 

Sur MessageHandler la mise en œuvre, nous devrons étendre Gestionnaire et mettre en œuvre le handleMessage (Message) méthode, où tous les messages seront traités. Notez que nous allons chercher Message.quel pour identifier le message, et nous obtenons également différents types de données de Message.obj. Passons rapidement en revue le plus important Message propriétés avant de plonger dans le code.

  • Message.quelint identifier le Message
  • Message.arg1int argument arbitraire
  • Message.arg2int argument arbitraire
  • Message.objObjet pour stocker différents types de données
Classe publique MessageActivity étend l'activité / ** * Le gestionnaire chargé de gérer la communication * à partir de @link WorkerThread. Il envoie des messages * à @link MessageActivity et les gère * ces messages * / public class MessageHandler extend Handler @Override public void handleMessage (Message msg) switch (msg.what) // gère le cas de l'image KEY_MSG_IMAGE:  Bitmap bmp = (Bitmap) msg.obj; myImage.setImageBitmap (bmp); Pause;  // gérer la casse d'appels ProgressBar KEY_MSG_PROGRESS: if ((boolean) msg.obj) progressBar.setVisibility (View.VISIBLE); else progressBar.setVisibility (View.GONE); Pause;  // traitement du pain grillé envoyé avec un cas de retard de message KEY_MSG_TOAST: String msgText = (String) msg.obj; Toast.makeText (getApplicationContext (), msgText, Toast.LENGTH_LONG) .show (); Pause;  // Gestionnaire permettant la communication // entre WorkerThread et Activity protected MessageHandler uiHandler;  

3.2 Envoi de messages avec WorkerThread

Revenons maintenant à la Travailleur classe. Nous ajouterons du code pour télécharger un bitmap spécifique et du code pour télécharger un fichier aléatoire. Pour accomplir ces tâches, nous vous enverrons Message objets de la Travailleur à lui-même et renvoyer les résultats à MessageActivité en utilisant exactement la même logique appliquée plus tôt pour la RunnableActivity.

Nous devons d’abord étendre la Gestionnaire traiter les messages téléchargés.

Classe publique WorkerThread étend HandlerThread // envoyer et traiter les messages à télécharger Messages sur le gestionnaire WorkerThread privé HandlerMsgImgDownloader handlerMsgImgDownloader; / ** * Clés permettant d'identifier les clés de @link Message # what * from Messages envoyés par @link # HandlerMsgImgDownloader * / private final int MSG_DOWNLOAD_IMG = 0; // msg qui télécharge une seule img private final int MSG_DOWNLOAD_RANDOM_IMG = 1; // msg qui télécharge au hasard img / ** * Gestionnaire responsable de la gestion du téléchargement de l'image * Il envoie et gère les messages identifiant puis utilisant * le @link Message # what * @link #MSG_DOWNLOAD_IMG: image unique * @link #MSG_DOWNLOAD_RANDOM_IMG: image aléatoire * / classe privée HandlerMsgImgDownloader étend Handler private HandlerMsgImgDownloader (boucle répétée) super (boucle répétée);  @Override public void handleMessage (Message msg) showProgressMSG (true); switch (msg.what) case MSG_DOWNLOAD_IMG: // reçoit une seule URL et la télécharge. String url = (String) msg.obj; downloadImageMSG (url); Pause;  case MSG_DOWNLOAD_RANDOM_IMG: // reçoit un String [] avec plusieurs URL // télécharge une image au hasard String [] urls = (String []) msg.obj; Aléatoire Aléatoire = Nouveau Aléatoire (); String url = urls [random.nextInt (urls.length)]; downloadImageMSG (url);  showProgressMSG (false); 

le downloadImageMSG (URL de la chaîne) méthode est fondamentalement la même que la downloadImage (URL de la chaîne) méthode. La seule différence est que le premier envoie le bitmap téléchargé à l'interface utilisateur en envoyant un message à l'aide de la commande responseHandler.

Classe publique WorkerThread étend HandlerThread / ** * Téléchargez une image bitmap à l'aide de son URL et * affichez-la dans l'interface utilisateur. * La seule différence avec @link #downloadImage (String) * est le fait qu'elle renvoie l'image à l'interface utilisateur * à l'aide d'un message * / private void downloadImageMSG (String urlStr) // Création d'une connexion HttpURLConnection connection = null; try URL url = new URL (urlStr); connexion = (HttpURLConnection) url.openConnection (); // récupère le flux depuis l'URL InputStream in = new BufferedInputStream (connection.getInputStream ()); final Bitmap bitmap = BitmapFactory.decodeStream (in); if (bitmap! = null) // envoie le bitmap téléchargé et un retour à l'interface utilisateur loadImageOnUIMSG (bitmap);  catch (IOException e) e.printStackTrace ();  finally if (connection! = null) connection.disconnect (); 

le loadImageOnUIMSG (image bitmap) est responsable de l'envoi d'un message avec le bitmap téléchargé à MessageActivité

 / ** * envoie un bitmap à l'interface utilisateur * en envoyant un message au @link #responseHandler * / private void loadImageOnUIMSG (image bitmap finale) if (checkResponse ()) sendMsgToUI (responseHandler.get (). getMessage ( MessageActivity.KEY_MSG_IMAGE, image));  / ** * Afficher / masquer progressBar sur l'interface utilisateur. * Il utilise @link #responseHandler pour * envoyer un message sur l'interface utilisateur * / private void showProgressMSG (émission booléenne) Log.d (TAG, "showProgressMSG ()"); if (checkResponse ()) sendMsgToUI (responseHandler.get (). getMessage (MessageActivity.KEY_MSG_PROGRESS, show)); 

Notez qu'au lieu de créer un Message objet à partir de zéro, nous utilisons le Handler.obtainMessage (int what, Object obj) méthode pour récupérer un Message du pool global, économisant des ressources. Il est également important de noter que nous appelons le obtenirMessage () sur le responseHandler, obtenir un Message associé à MessageActivitéde Looper. Il y a deux façons de récupérer un Message du pool global: Message.obtain () et Handler.obtainMessage ().

La seule chose qui reste à faire dans la tâche de téléchargement d’image est de fournir les méthodes pour envoyer un message. Message à Travailleur pour démarrer le processus de téléchargement. Notez que cette fois nous appellerons Message.obtain (gestionnaire de gestionnaire, int quoi, objet obj) sur handlerMsgImgDownloader, associer le message avec Travailleurle looper.

 / ** * envoie un message au thread actuel * en utilisant @link #handlerMsgImgDownloader * pour télécharger une seule image. * / public void downloadWithMessage () Log.d (TAG, "downloadWithMessage ()"); showOperationOnUIMSG ("Envoi de message…"); if (handlerMsgImgDownloader == null) handlerMsgImgDownloader = new HandlerMsgImgDownloader (getLooper ()); Message message = Message.obtain (handlerMsgImgDownloader, MSG_DOWNLOAD_IMG, imageBUrl); handlerMsgImgDownloader.sendMessage (message);  / ** * envoie un message à la discussion en cours * en utilisant @link #handlerMsgImgDownloader * pour télécharger une image aléatoire. * / public void downloadRandomWithMessage () Log.d (TAG, "downloadRandomWithMessage ()"); showOperationOnUIMSG ("Envoi de message…"); if (handlerMsgImgDownloader == null) handlerMsgImgDownloader = new HandlerMsgImgDownloader (getLooper ()); Message message = Message.obtain (handlerMsgImgDownloader, MSG_DOWNLOAD_RANDOM_IMG, imagesUrls); handlerMsgImgDownloader.sendMessage (message); 

Une autre possibilité intéressante est l'envoi Message objets à traiter ultérieurement avec la commande Message.sendMessageDelayed (Message msg, long timeMillis).

/ ** * Afficher un pain grillé après un délai. * * envoie un message avec délai sur le WorkerThread * et envoie un nouveau message à @link MessageActivity * avec un texte après le traitement du message * / public void startMessageDelay () // message delay long delay = 5000; String msgText = "Bonjour de la part de WorkerThread!"; // Gestionnaire responsable de l'envoi du message à WorkerThread // à l'aide de Handler.Callback () afin d'éviter la nécessité d'étendre la classe Handler handler handler = new Handler (new Handler.Callback () @Override public boolean handleMessage (Message msg) responseHandler .get (). sendMessage (responseHandler.get (). obtenirMessage (MessageActivity.KEY_MSG_TOAST, msg.obj)); return true;); // envoi du message handler.sendMessageDelayed (handler.obtainMessage (0, msgText), delay); 

Nous avons créé un Gestionnaire expressément pour l'envoi du message différé. Au lieu d’étendre la Gestionnaire classe, nous avons pris la voie d'instanciation d'un Gestionnaire en utilisant le Handler.Callback interface, pour laquelle nous avons implémenté le handleMessage (Message msg) méthode pour traiter le retard Message.

4. Conclusion

Vous avez déjà vu suffisamment de code pour comprendre comment appliquer les concepts de base du cadre HaMeR pour gérer la concurrence sur Android. Il y a d'autres fonctionnalités intéressantes du projet final stockées sur GitHub, et je vous conseille vivement de le vérifier.. 

Enfin, j'ai quelques dernières considérations à prendre en compte:

  • N'oubliez pas de prendre en compte le cycle de vie de l'activité Android lorsque vous travaillez avec HaMeR et Threads en général. Sinon, votre application peut échouer lorsque le thread tente d'accéder à des activités détruites en raison de modifications de la configuration ou pour d'autres raisons. Une solution courante consiste à utiliser un Fragment retenu pour stocker le fil et remplir le fil d'arrière-plan avec la référence de l'activité chaque fois que l'activité est détruite. Regardez la solution dans le projet final sur GitHub.
  • Les tâches qui s'exécutent en raison de Runnable et Message objets traités sur Manutentionnaires ne pas exécuter de manière asynchrone. Ils fonctionneront de manière synchrone sur le thread associé au gestionnaire. Pour le rendre asynchrone, vous devez créer un autre thread, envoyer / poster le Message/Runnable objecter dessus et recevoir les résultats au moment opportun.

Comme vous pouvez le constater, le framework HaMeR offre de nombreuses possibilités et constitue une solution assez ouverte avec de nombreuses options pour gérer la concurrence sur Android. Ces caractéristiques peuvent être des avantages sur AsyncTask, en fonction de vos besoins. Explorez davantage le cadre et lisez la documentation pour créer de superbes choses..

À bientôt!