In Understanding Concurrency op Android met HaMeR hebben we gesproken over de basisprincipes van de HaMeR (handler
, Bericht
, en uitvoerbare
) kader. We hebben de opties behandeld, evenals wanneer en hoe het te gebruiken.
Vandaag zullen we een eenvoudige applicatie maken om de geleerde concepten te verkennen. Met een praktische benadering zullen we zien hoe we de verschillende mogelijkheden van HaMeR kunnen toepassen bij het beheren van concurrency op Android.
Laten we aan de slag gaan en wat posten uitvoerbare
en verzend Bericht
objecten op een voorbeeldtoepassing. Om het zo eenvoudig mogelijk te houden, zullen we alleen de meest interessante delen verkennen. Alle bronbestanden en standaardactiviteitsoproepen worden hier genegeerd. Daarom raad ik u ten zeerste aan de broncode van de voorbeeldtoepassing te bekijken met zijn uitgebreide opmerkingen.
De app zal bestaan uit:
uitvoerbare
een andere voor Bericht
callsHandlerThread
voorwerpen:WorkerThread
om oproepen van de gebruikersinterface te ontvangen en te verwerkenCounterThread
ontvangen Bericht
oproepen van de WorkerThread
Laten we beginnen met het experimenteren met de Handler.post (Runnable)
methode en de bijbehorende variaties, die een a toevoegen aan een message queue
geassocieerd met een thread. We maken een activiteit genaamd RunnableActivity
, die communiceert met een geroepen achtergronddraad WorkerThread
.
De RunnableActivity
maakt een achtergronddraad die wordt aangeroepen WorkerThread
, passeren van een handler
en een WorkerThread.Callback
als parameters. De activiteit kan oproepen aannemen WorkerThread
om asynchroon een bitmap te downloaden en op een bepaald moment een toast te tonen. De resultaten van de taken die door de werkthread worden uitgevoerd, worden doorgegeven aan RunnableActivity
door runnables geplaatst op de handler
ontvangen door WorkerThread
.
Op de RunnableActivity
we zullen een maken handler
worden doorgegeven aan WorkerThread
. De uiHandler
zal worden geassocieerd met de lussenmaker
van de UI-thread, omdat het wordt aangeroepen vanuit die thread.
public class RunnableActivity breidt Activiteit uit // Handler die communicatie tussen // the WorkerThread en de Activity-handler uiHandler mogelijk maakt; @Override protected void onCreate (Bundle savedInstanceState) super.onCreate (savedInstanceState); setContentView (R.layout.activity_main); // bereid de UI-handler voor op verzenden naar WorkerThread uiHandler = new Handler ();
WorkerThread
en de terugbelinterfaceDe WorkerThread
is een achtergrondthema waar we verschillende soorten taken zullen starten. Het communiceert met de gebruikersinterface met behulp van de responseHandler
en een terugbelinterface ontvangen tijdens de instantiatie ervan. De referenties ontvangen van de activiteiten zijn WeakReference <>
type, omdat een activiteit kan worden vernietigd en de referentie verloren gaat.
De klasse biedt een interface die kan worden geïmplementeerd door de gebruikersinterface. Het breidt zich ook uit HandlerThread
, een helperklasse gebouwd bovenop Draad
die al een bevat lussenmaker
, en een message queue
. Vandaar dat het correct is opstellingom het HaMeR-raamwerk te gebruiken.
openbare klasse WorkerThread breidt HandlerThread / ** * -interface uit om oproepen in de gebruikersinterface mogelijk te maken. * / openbare interface Callback void loadImage (Bitmap-afbeelding); void showToast (String msg); // Deze handler is alleen verantwoordelijk // voor het plaatsen van Runnables op deze Thread private Handler postHandler; // Handler wordt ontvangen van de MessageActivity en RunnableActivity // die verantwoordelijk zijn voor het ontvangen van bruikbare oproepen die worden verwerkt // in de gebruikersinterface. De callback zal dit proces helpen. private WeakReferenceresponseHandler; // Terugbellen vanuit de gebruikersinterface // het is een WeakReference omdat het ongeldig kan worden // tijdens "configuratiewijzigingen" en andere gebeurtenissen private WeakReference Bel terug; private final String imageAUrl = "https://pixabay.com/static/uploads/photo/2016/08/05/18/28/mobile-phone-1572901_960_720.jpg"; / ** * De constructor ontvangt een handler en een terugroepactie van de gebruikersinterface * @param responseHandler die verantwoordelijk is voor het plaatsen van het uitvoerbare bestand naar de UI * @param callback werkt samen met de responseHandler * waarmee rechtstreeks kan worden gebeld op de UI * / public WorkerThread (handler) responseHandler, callback callback) super (TAG); this.responseHandler = new WeakReference <> (responseHandler); this.callback = new WeakReference <> (callback);
WorkerThread
We moeten een methode toevoegen aan WorkerThread
worden aangeroepen door de activiteiten die de thread voorbereiden postHandler
voor gebruik. De methode hoeft alleen te worden aangeroepen nadat de thread is gestart.
public class WorkerThread breidt HandlerThread uit / ** * Bereid de postHandler voor. * Het moet worden opgeroepen nadat de thread is gestart * / public void prepareHandler () postHandler = new Handler (getLooper ());
Op de RunnableActivity
we moeten implementeren WorkerThread.Callback
en initialiseer de thread zodat deze kan worden gebruikt.
public class RunnableActivity breidt activiteiten uit Werkt WorkThread.Callback // BackgroundThread verantwoordelijk voor het downloaden van de image protected WorkerThread workerThread; / ** * Initialiseer de @link WorkerThread -instantie * alleen als deze nog niet is geïnitialiseerd. * / public void initWorkerThread () if (workerThread == null) workerThread = new WorkerThread (uiHandler, this); workerThread.start (); workerThread.prepareHandler (); / ** * zet de afbeelding die is gedownload op bg-thread naar de imageView * / @Override public void loadImage (Bitmap image) myImage.setImageBitmap (image); @Override openbare ongeldige showToast (laatste string-msg) // te implementeren
Handler.post ()
op de WorkerThreadDe WorkerThread.downloadWithRunnable ()
methode downloadt een bitmap en stuurt deze naar RunnableActivity
worden weergegeven in een afbeeldingsweergave. Het illustreert twee basisgebruiken van de Handler.post (loopbare run)
commando:
.post()
wordt aangeroepen op een handler die is gekoppeld aan de Thread's Looper..post()
wordt aangeroepen op een handler die is gekoppeld aan andere Thread's Looper. WorkerThread.downloadWithRunnable ()
methode posts a uitvoerbare
naar de WorkerThread
's message queue
de ... gebruiken postHandler
, een handler
geassocieerd met WorkThread
's lussenmaker
.Bitmap
op de WorkerThread
.responseHandler
, een handler die is gekoppeld aan de UI-thread, wordt gebruikt om een run te plaatsen op de RunnableActivity
met de bitmap.WorkerThread.Callback.loadImage
wordt gebruikt om de gedownloade afbeelding weer te geven op een Figuurweergave
.public class WorkerThread breidt HandlerThread uit / ** * post een Runnable naar de WorkerThread * Download een bitmap en verzend de afbeelding * naar de UI @link RunnableActivity * met behulp van de @link #responseHandler met * help van de @link #callback * / public void downloadWithRunnable () // post Runnable to WorkerThread postHandler.post (nieuw Runnable () @Override public void run () try // slaapt gedurende 2 seconden om een langlopende bewerking te emuleren TimeUnit.SECONDS .sleep (2); // Download afbeelding en verzendt naar UI downloadImage (imageAUrl); catch (InterruptedException e) e.printStackTrace ();); / ** * Download een bitmap met behulp van de URL en * stuur naar de UI de gedownloade afbeelding * / private void downloadImage (String urlStr) // Maak een verbinding HttpURLConnection connection = null; probeer URL url = nieuwe URL (urlStr); connection = (HttpURLConnection) url.openConnection (); // haal de stream uit de url InputStream in = new BufferedInputStream (connection.getInputStream ()); laatste Bitmap bitmap = BitmapFactory.decodeStream (in); if (bitmap! = null) // stuur de bitmap gedownload en een feedback naar de UI loadImageOnUI (bitmap); else catch (IOException e) e.printStackTrace (); eindelijk if (connection! = null) connection.disconnect (); / ** * stuurt een Bitmap naar de ui * een bericht naar de @link #responseHandler * posting en gebruikt de @link Callback * / private void loadImageOnUI (laatste bitmapafbeelding) Log.d (TAG, "loadImageOnUI (" + afbeelding + ")"); if (checkResponse ()) responseHandler.get (). post (nieuw Runnable () @Override public void run () callback.get (). loadImage (image);); // verifieer of responseHandler beschikbaar is // zo niet, dan gaat de activiteit voorbij aan een of andere vernietigingsgebeurtenis. private boolean checkResponse () return responseHandler.get ()! = null;
Handler.postAtTime ()
en Activity.runOnUiThread ()
De WorkerThread.toastAtTime ()
plant een taak die op een bepaald moment moet worden uitgevoerd, met een Geroosterd brood
voor de gebruiker. De methode illustreert het gebruik van de Handler.postAtTime ()
en de Activity.runOnUiThread ()
.
Handler.postAtTime (Loopbare run, lange uptimeMillis)
posts een loopbare op een gegeven moment.Activity.runOnUiThread (Runnable run)
gebruikt de standaard UI-handler om een variabele naar de hoofdthread te posten.public class WorkerThread breidt HandlerThread uit / ** * toont een Toast in de gebruikersinterface. * plant de taak op basis van de huidige tijd. * Het kan op elk moment worden gepland, we gebruiken * 5 seconden om het debuggen te vergemakkelijken * / public void toastAtTime () Log.d (TAG, "toastAtTime (): current -" + Calendar.getInstance (). ToString ()); // seconden om toe te voegen op de huidige tijd int delaySeconds = 5; // testing using a real date Calendar scheduledDate = Calendar.getInstance (); / / instellen van een datum in de toekomst gezien de vertraging in seconden definiëren // we gebruiken deze aanpak alleen maar om het testen te vergemakkelijken. // het kan worden gedaan met behulp van een door de gebruiker gedefinieerde datum ook scheduledDate.set (scheduledDate.get (Calendar.YEAR), scheduledDate.get (Calendar.MONTH), scheduledDate.get (Calendar.DAY_OF_MONTH), scheduledDate.get (Calendar.HOUR_OF_DAY ), scheduledDate.get (Calendar.MINUTE), scheduledDate.get (Calendar.SECOND) + delaySeconds); Log.d (TAG, "toastAtTime (): planning op -" + scheduledDate.toString ()); lang gepland = calculationUptimeMillis (scheduledDate); // posting Runnable op specifieke tijd postHandler.postAtTime (nieuw Runnable () @Override public void run () if (callback! = null) callback.get (). showToast ("Toast called using 'postAtTime ()'. ");, gepland); / ** * Berekent de @link SystemClock # uptimeMillis () tot * een bepaalde kalenderdatum. * / private lange calculationUptimeMillis (Calendar calendar) long time = calendar.getTimeInMillis (); long currentTime = Calendar.getInstance (). getTimeInMillis (); long diff = time - currentTime; return SystemClock.uptimeMillis () + diff;
public class RunnableActivity breidt activiteiten uit WorkerThread.Callback / ** * Callback van @link WorkerThread * Gebruik @link #runOnUiThread (Runnable) om * dergelijke methode * / @Override public-holt showToast (slot-string msg) Log.d (TAG, "showToast (" + msg + ")"); runOnUiThread (nieuw Runnable () @Override public void run () Toast.makeText (getApplicationContext (), msg, Toast.LENGTH_LONG) .show (););
MessageActivity
& WorkerThread
Laten we vervolgens een aantal verschillende manieren van gebruik verkennen MessageActivity
verzenden en verwerken Bericht
voorwerpen. De MessageActivity
concretiseert WorkerThread
, passeren van een handler
als een parameter. De WorkerThread
heeft een aantal openbare methoden met taken die door de activiteit moeten worden aangeroepen om een bitmap te downloaden, een willekeurige bitmap te downloaden of een a te vertonen Geroosterd brood
na enige uitgestelde tijd. De resultaten van al die bewerkingen worden teruggestuurd naar MessageActivity
gebruik makend van Bericht
objecten verzonden door de responseHandler
.
MessageActivity
Als in de RunnableActivity
, in de MessageActivity
we zullen een a moeten instantiëren en initialiseren WorkerThread
verzenden van een handler
om gegevens van de achtergronddraad te ontvangen. Deze keer zullen we echter niet implementeren WorkerThread.Callback
; in plaats daarvan ontvangen we reacties van de WorkerThread
exclusief door Bericht
voorwerpen.
Aangezien de meeste van de MessageActivity
en RunnableActivity
code is in principe hetzelfde, we concentreren ons alleen op de uiHandler
voorbereiding, die wordt verzonden naar WorkerThread
om er berichten van te ontvangen.
Laten we eerst wat verstrekken int
toetsen die moeten worden gebruikt als ID's voor de Message-objecten.
public class MessageActivity breidt Activiteit uit // Bericht-ID gebruikt op Message.what () veld public static final int KEY_MSG_IMAGE = 2; public static final int KEY_MSG_PROGRESS = 3; public static final int KEY_MSG_TOAST = 4;
Op messageHandler
implementatie, moeten we uitbreiden handler
en implementeer de handleMessage (Message)
methode, waar alle berichten worden verwerkt. Merk op dat we aan het halen zijn Message.what
om het bericht te identificeren en we krijgen ook verschillende soorten gegevens van Message.obj
. Laten we snel de belangrijkste bekijken Bericht
eigenschappen voordat u in de code duikt.
Message.what
: int
het identificeren van de Bericht
Message.arg1
: int
willekeurig argumentMessage.arg2
: int
willekeurig argumentMessage.obj
: Voorwerp
om verschillende soorten gegevens op te slaanpublic class MessageActivity breidt activiteit uit / ** * Handler verantwoordelijk voor het beheren van communicatie * vanuit de @link WorkerThread. Het stuurt Berichten * terug naar de @link MessageActivity en verwerkt * die Berichten * / public class MessageHandler verlengt Handler @Override public void handleMessage (Message msg) switch (msg.what) // behandel afbeelding case KEY_MSG_IMAGE: Bitmap bmp = (Bitmap) msg.obj; myImage.setImageBitmap (BMP); breken; // handle progressBar calls case KEY_MSG_PROGRESS: if ((boolean) msg.obj) progressBar.setVisibility (View.VISIBLE); else progressBar.setVisibility (View.GONE); breken; // omgaan met toast verzonden met een berichtvertragingsgeval KEY_MSG_TOAST: String msgText = (String) msg.obj; Toast.makeText (getApplicationContext (), msgText, Toast.LENGTH_LONG) .show (); breken; // Handler voor communicatie tussen // the WorkerThread en de activiteitsbeschermde MessageHandler uiHandler;
Laten we nu teruggaan naar de WorkerThread
klasse. We zullen een code toevoegen om een specifieke bitmap te downloaden en ook code om een willekeurige bitmap te downloaden. Om deze taken te volbrengen, sturen we Bericht
objecten uit de WorkerThread
naar zichzelf en stuur de resultaten terug naar MessageActivity
met behulp van exact dezelfde logica eerder toegepast voor de RunnableActivity
.
Eerst moeten we het handler
om de gedownloade berichten te verwerken.
openbare klasse WorkerThread breidt HandlerThread uit // verzendt en verwerkt downloadberichten Berichten op de WorkerThread privé HandlerMsgImgDownloader handlerMsgImgDownloader; / ** * Toetsen om de sleutels van @link Message # what * te identificeren van berichten die zijn verzonden door @link #handlerMsgImgDownloader * / private final int MSG_DOWNLOAD_IMG = 0; // msg dat download een enkele img private finale int MSG_DOWNLOAD_RANDOM_IMG = 1; // msg die willekeurige img downloaden / ** * Handler die verantwoordelijk is voor het beheren van de afbeeldingdownload * Het verzendt en verwerkt berichten die identificeren en gebruikt vervolgens * het @link Message # what * @link #MSG_DOWNLOAD_IMG: één afbeelding * @link #MSG_DOWNLOAD_RANDOM_IMG: willekeurige afbeelding * / private class HandlerMsgImgDownloader breidt Handler uit private HandlerMsgImgDownloader (Looper looper) super (looper); @Override public void handleMessage (Message msg) showProgressMSG (true); switch (msg.what) case MSG_DOWNLOAD_IMG: // ontvangt één URL en download deze String url = (String) msg.obj; downloadImageMSG (url); breken; case MSG_DOWNLOAD_RANDOM_IMG: // ontvangt een String [] met meerdere urls // downloadt willekeurig een afbeelding String [] urls = (String []) msg.obj; Willekeurig willekeurig = nieuw Willekeurig (); String url = urls [random.nextInt (urls.length)]; downloadImageMSG (url); showProgressMSG (false);
De downloadImageMSG (String-URL)
methode is in principe hetzelfde als de downloadImage (reeks-URL)
methode. Het enige verschil is dat de eerste de gedownloade bitmap terugzendt naar de gebruikersinterface door een bericht te verzenden met behulp van de responseHandler
.
openbare klasse WorkerThread breidt HandlerThread uit / ** * Download een bitmap met behulp van de URL en * geef deze weer aan de gebruikersinterface. * Het enige verschil met @link #downloadImage (String) * is dat het de afbeelding terugstuurt naar de UI * met een Message * / private void downloadImageMSG (String urlStr) // Maak een verbinding HttpURLConnection connection = null; probeer URL url = nieuwe URL (urlStr); connection = (HttpURLConnection) url.openConnection (); // haal de stream uit de url InputStream in = new BufferedInputStream (connection.getInputStream ()); laatste Bitmap bitmap = BitmapFactory.decodeStream (in); if (bitmap! = null) // stuur de bitmap gedownload en een feedback naar de UI loadImageOnUIMSG (bitmap); catch (IOException e) e.printStackTrace (); eindelijk if (connection! = null) connection.disconnect ();
De loadImageOnUIMSG (Bitmap-afbeelding)
is verantwoordelijk voor het verzenden van een bericht met de gedownloade bitmap naar MessageActivity
.
/ ** * stuurt een Bitmap naar de ui * een bericht verzenden naar de @link #responseHandler * / private void loadImageOnUIMSG (laatste bitmapafbeelding) if (checkResponse ()) sendMsgToUI (responseHandler.get (). obtainMessage ( MessageActivity.KEY_MSG_IMAGE, afbeelding)); / ** * Show / Hide progressBar in de gebruikersinterface. * Het gebruikt de @link #responseHandler om * een bericht te verzenden op de UI * / private void showProgressMSG (booleaanse show) Log.d (TAG, "showProgressMSG ()"); if (checkResponse ()) sendMsgToUI (responseHandler.get (). obtainMessage (MessageActivity.KEY_MSG_PROGRESS, show));
Merk op dat in plaats van het creëren van een Bericht
object from scratch, we gebruiken de Handler.obtainMessage (int what, Object obj)
methode om een te verkrijgen Bericht
van de globale pool, wat middelen bespaart. Het is ook belangrijk op te merken dat we de obtainMessage ()
op de responseHandler
, verkrijgen van een Bericht
geassocieerd met MessageActivity
's lussenmaker
. Er zijn twee manieren om een te verkrijgen Bericht
uit de global pool: Message.obtain ()
en Handler.obtainMessage ()
.
Het enige dat u nog moet doen aan de downloadtaak voor afbeeldingen, is het bieden van de verzendmethoden a Bericht
naar WorkerThread
om het downloadproces te starten. Merk op dat we dit keer zullen bellen Message.obtain (Handlerhandler, int what, Object obj)
op handlerMsgImgDownloader
, associëren van het bericht met WorkerThread
looper.
/ ** * stuurt een bericht naar de huidige thread * met behulp van de @link #handlerMsgImgDownloader * om één afbeelding te downloaden. * / public void downloadWithMessage () Log.d (TAG, "downloadWithMessage ()"); showOperationOnUIMSG ("Bericht verzenden ..."); if (handlerMsgImgDownloader == null) handlerMsgImgDownloader = new HandlerMsgImgDownloader (getLooper ()); Message message = Message.obtain (handlerMsgImgDownloader, MSG_DOWNLOAD_IMG, imageBUrl); handlerMsgImgDownloader.sendMessage (bericht); / ** * stuurt een bericht naar de huidige thread * met behulp van de @link #handlerMsgImgDownloader * om een willekeurige afbeelding te downloaden. * / public void downloadRandomWithMessage () Log.d (TAG, "downloadRandomWithMessage ()"); showOperationOnUIMSG ("Bericht verzenden ..."); if (handlerMsgImgDownloader == null) handlerMsgImgDownloader = new HandlerMsgImgDownloader (getLooper ()); Berichtbericht = Message.obtain (handlerMsgImgDownloader, MSG_DOWNLOAD_RANDOM_IMG, imagesUrls); handlerMsgImgDownloader.sendMessage (bericht);
Een andere interessante mogelijkheid is verzenden Bericht
objecten die op een later tijdstip moeten worden verwerkt met het commando Message.sendMessageDelayed (Message msg, long timeMillis)
.
/ ** * Laat een toast zien na een uitgestelde tijd. * * stuur een bericht met vertraagde tijd op de WorkerThread * en verzendt een nieuw bericht naar @link MessageActivity * met een tekst nadat het bericht is verwerkt * / public void startMessageDelay () // bericht vertraging lange vertraging = 5000; String msgText = "Hallo van WorkerThread!"; // Handler die verantwoordelijk is voor het verzenden van Message naar WorkerThread // met Handler.Callback () om te voorkomen dat de handlerklasse moet worden uitgebreid Handlerhandler = new Handler (nieuwe handler.Callback () @Override public Boolean handleMessage uitschakelen (berichtbericht) responseHandler .get (). sendMessage (responseHandler.get (). obtainMessage (MessageActivity.KEY_MSG_TOAST, msg.obj)); return true;); // send message handler.sendMessageDelayed (handler.obtainMessage (0, msgText), delay);
We hebben een gemaakt handler
uitdrukkelijk voor het verzenden van het vertraagde bericht. In plaats van de extensie uit te breiden handler
klas, we hebben de route van instantiëren genomen handler
door de Handler.Callback
interface, waarvoor we het handleMessage (berichtbericht)
methode om vertragingen te verwerken Bericht
.
Je hebt nu genoeg code gezien om te begrijpen hoe je de basale HaMeR-raamwerkconcepten kunt toepassen om concurrency op Android te beheren. Er zijn enkele andere interessante kenmerken van het uiteindelijke project opgeslagen op GitHub, en ik adviseer je ten stelligste om het te bekijken.
Tot slot, ik heb enkele laatste overwegingen die u in gedachten moet houden:
RetainedFragment
om de thread op te slaan en de achtergrondthread te vullen met de verwijzing naar de activiteit telkens wanneer de activiteit wordt vernietigd. Bekijk de oplossing in het laatste project op GitHub.uitvoerbare
en Bericht
objecten verwerkt op handlers
niet asynchroon uitvoeren. Ze worden synchroon uitgevoerd op de thread die aan de handler is gekoppeld. Als u het asynchroon wilt maken, moet u nog een thread maken, het verzenden / plaatsen Bericht
/uitvoerbare
object erop en ontvang de resultaten op het juiste moment.Zoals je ziet, heeft het HaMeR-framework veel verschillende mogelijkheden en het is een vrij open oplossing met veel opties voor het beheren van concurrency op Android. Deze kenmerken kunnen voordelen opleveren AsyncTask
, afhankelijk van uw behoeften. Verken meer van het framework en lees de documentatie, en je zult er geweldige dingen mee creëren.
Tot ziens!