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.
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:
Runnable
un autre pour Message
appelsHandlerThread
objets:Travailleur
recevoir et traiter les appels de l'interface utilisateurContre-fil
recevoir Message
les appels du Travailleur
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
.
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 ();
Travailleur
et son interface de rappelle 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);
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
Handler.post ()
sur le WorkerThreadle 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:
.poster()
est appelé sur un gestionnaire associé au fileur du fil..poster()
est appelé sur un gestionnaire associé à un autre utilisateur du fil d'exécution. WorkerThread.downloadWithRunnable ()
méthode affiche un Runnable
au Travailleur
de MessageQueue
en utilisant le postHandler
, une Gestionnaire
associé à Fil de travail
de Looper
.Bitmap
sur le Travailleur
.responseHandler
, un gestionnaire associé au thread d'interface utilisateur, est utilisé pour poster un exécutable sur le RunnableActivity
contenant le bitmap.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;
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 (););
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
.
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.quel
: int
identifier le Message
Message.arg1
: int
argument arbitraireMessage.arg2
: int
argument arbitraireMessage.obj
: Objet
pour stocker différents types de donnéesClasse 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;
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 Travailleur
le 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
.
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:
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.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!