Vid förståelse av samtidighet på Android Med hjälp av HaMeR talade vi om grunderna i HaMeR (Hanterare
, Meddelande
, och Runnable
) ramverk. Vi täckte dess alternativ, samt när och hur man använder den.
Idag skapar vi en enkel applikation för att utforska de begrepp som lärt sig. Med ett praktiskt tillvägagångssätt ser vi hur man tillämpar de olika möjligheterna hos HaMeR i hantering av samtidighet på Android.
Låt oss komma till jobbet och skicka några Runnable
och skicka Meddelande
objekt på en provapplikation. För att hålla det så enkelt som möjligt, utforskar vi bara de mest intressanta delarna. Alla resursfiler och standardaktivitetssamtal kommer att ignoreras här. Så jag rekommenderar starkt dig att kolla källkoden för provprogrammet med sina omfattande kommentarer.
Appen kommer att bestå av:
Runnable
en annan för Meddelande
samtalHandlerThread
objekt:WorkerThread
att ta emot och behandla samtal från användargränssnittetmotgänga
att motta Meddelande
samtal från WorkerThread
Låt oss börja experimentera med Handler.post (Runnable)
metod och dess variationer, som lägger till en runnable till a Meddelandekö
associerad med en tråd. Vi skapar en aktivitet som heter RunnableActivity
, som kommunicerar med en bakgrundsgänga som heter WorkerThread
.
De RunnableActivity
instantiates en bakgrundsgänga som heter WorkerThread
, passerar a Hanterare
och a WorkerThread.Callback
som parametrar. Verksamheten kan ringa upp WorkerThread
att asynkront ladda ner en bitmapp och visa en toast vid en viss tidpunkt. Resultaten av de uppgifter som gjorts av arbetstråden skickas till RunnableActivity
av runnables publicerade på Hanterare
mottaget av WorkerThread
.
På RunnableActivity
vi ska skapa en Hanterare
att vidarebefordras till WorkerThread
. De uiHandler
kommer att vara associerad med Looper
från användargränssnittet, eftersom det kallas från den tråden.
allmän klass RunnableActivity utökar aktivitet // Handler som tillåter kommunikation mellan // WorkerThread och Aktivitetsskyddad Handler uiHandler; @Override protected void onCreate (Bundle savedInstanceState) super.onCreate (savedInstanceState); setContentView (R.layout.activity_main); // förbereda UI Handler för att skicka till WorkerThread uiHandler = new Handler ();
WorkerThread
och dess återkopplingsgränssnittDe WorkerThread
är en bakgrundstråd där vi ska starta olika typer av uppgifter. Den kommunicerar med användargränssnittet med hjälp av responseHandler
och ett återkopplingsgränssnitt mottaget under dess instansiering. Referenser från verksamheten är WeakReference <>
typ, eftersom en aktivitet kan förstöras och referensen förloras.
Klassen erbjuder ett gränssnitt som kan implementeras av användargränssnittet. Det sträcker sig också HandlerThread
, en hjälparklass byggd ovanpå Tråd
som redan innehåller en Looper
, och a Meddelandekö
. Därför har den rätt inrättaatt använda HaMeR-ramverket.
offentlig klass WorkerThread utökar HandlerThread / ** * Interface för att underlätta samtal på användargränssnittet. * / public interface Återuppringning void loadImage (Bitmap image); void showToast (String msg); // Den här hanteraren är endast ansvarig // för att posta Runnables på den här Thread Private Handler postHandler; // Handler är mottagen från MessageActivity och RunnableActivity // ansvarig för att ta emot Runnable-samtal som ska behandlas // på användargränssnittet. Återuppringningen hjälper till med denna process. privat weakreferenceresponseHandler; // Återuppringning från UI // det är en WeakReference eftersom det kan ogiltigförklaras // under "konfigurationsändringar" och andra händelser privata WeakReference ring tillbaka; privat sista sträng imageAUrl = "https://pixabay.com/static/uploads/photo/2016/08/05/18/28/mobile-phone-1572901_960_720.jpg"; / ** * Konstruktören tar emot en hanterare och en återuppringning från användargränssnittet * @param responseHandler ansvarig för att skicka Runnable till användargränssnittet * @param callback fungerar tillsammans med responseHandler * tillåter samtal direkt på UI * / Public WorkerThread (HandlerThread responseHandler, återuppringning) super (TAG); this.responseHandler = new WeakReference <> (responseHandler); this.callback = new WeakReference <> (callback);
WorkerThread
Vi måste lägga till en metod till WorkerThread
att kallas av de aktiviteter som förbereder tråden postHandler
för användning. Metoden behöver endast kallas efter att tråden är igång.
offentlig klass WorkerThread utökar HandlerThread / ** * Förbered posthanteraren. * Det måste kallas efter att tråden har startat * / public void preparationHandler () postHandler = new Handler (getLooper ());
På RunnableActivity
vi måste genomföra WorkerThread.Callback
och initiera tråden så att den kan användas.
offentlig klass RunnableActivity utökar Aktivitetsverktyg WorkerThread.Callback // BackgroundThread ansvarig för nedladdning av den bildskyddade WorkerThread workerThread; / ** * Initiera @link WorkerThread -exemplet * endast om det inte har initialiserats än. * / public void initWorkerThread () if (workerThread == null) workerThread = new WorkerThread (uiHandler, detta); workerThread.start (); workerThread.prepareHandler (); / ** * Ställ in bilden som laddats ner på bg-tråden till bildvynn / / Överför allmän tomtlastImage (Bitmap-bild) myImage.setImageBitmap (bild); @Override public void showToast (finalstring msg) // för att implementeras
Handler.post ()
på WorkerThreadDe WorkerThread.downloadWithRunnable ()
Metoden hämtar en bitmapp och skickar den till RunnableActivity
att visas i en bildvisning. Det illustrerar två grundläggande användningsområden för Handler.post (Runnable Run)
kommando:
.posta()
kallas på en Handler i samband med trådens Looper..posta()
kallas på en Handler som är associerad med andra trådens Looper. WorkerThread.downloadWithRunnable ()
metodposter a Runnable
till WorkerThread
's Meddelandekö
använda postHandler
, en Hanterare
associerad med WorkThread
's Looper
.Bitmap
på WorkerThread
.responseHandler
, en hanterare som är associerad med användargränssnittet, används för att lägga in en runnable på RunnableActivity
innehållande bitmappen.WorkerThread.Callback.loadImage
används för att visa den nedladdade bilden på en Imageview
.public class WorkerThread utökar HandlerThread / ** * posta en Runnable till WorkerThread * Ladda ner en bitmapp och skicka bilden * till användargränssnittet @link RunnableActivity * med hjälp av @link #responseHandler med * hjälp från @link #callback * / public void downloadWithRunnable () // post Runnable till WorkerThread postHandler.post (nytt Runnable () @Override public void run () försök // sover i 2 sekunder för att emulera lång löpande drift TimeUnit.SECONDS .sleep (2); // Ladda ner bild och skickar till UI downloadImage (imageAUrl); catch (InterruptedException e) e.printStackTrace ();); / ** * Ladda ner en bitmapp med sin webbadress och * skicka till den grafiska bilden den nedladdade bilden * / privat tomt downloadImage (String urlStr) // Skapa en anslutning HttpURLConnection connection = null; prova URL url = ny URL (urlStr); anslutning = (HttpURLConnection) url.openConnection (); // få strömmen från url InputStream in = nya BufferedInputStream (connection.getInputStream ()); Final Bitmap bitmap = BitmapFactory.decodeStream (in); om (bitmapp! = null) // skicka bitmappen nerladdad och en återkoppling till UI loadImageOnUI (bitmapp); annars fånga (IOException e) e.printStackTrace (); äntligen om (anslutning! = null) connection.disconnect (); / ** * skickar en bitmapp till ui * postar en Runnable till @link #responseHandler * och använder @link Callback * / privat void loadImageOnUI (slutlig bitmappsbild) Log.d (TAG, "loadImageOnUI (" + bild + ")"); om (checkResponse ()) responseHandler.get (). post (nytt Runnable () @Override public void run () callback.get (). loadImage (bild);); // verifiera om responseHandler är tillgänglig // om inte aktiviteten passerar någon förstörelse händelse privat booleansk checkResponse () return responseHandler.get ()! = null;
Handler.postAtTime ()
och Activity.runOnUiThread ()
De WorkerThread.toastAtTime ()
schemalägger en uppgift som ska utföras vid en viss tid och uppvisar en Rostat bröd
till användaren. Metoden illustrerar användningen av Handler.postAtTime ()
och den Activity.runOnUiThread ()
.
Handler.postAtTime (Runnable Run, lång upptidMillis)
postar en runnable vid en given tidpunkt.Activity.runOnUiThread (Runnable run)
använder standardgränssnittshanteraren för att skicka en runnbar till huvudtråden.offentlig klass WorkerThread utökar HandlerThread / ** * visar en Toast på UI. * schemalägger uppgiften med tanke på den aktuella tiden. * Det kan vara planerat när som helst, vi använder * 5 sekunder för att underlätta debugging * / public void toastAtTime () Log.d (TAG, "toastAtTime (): current -" + Calendar.getInstance (). ToString ()); // sekunder att lägga till vid aktuell tid int delaySeconds = 5; // testning med ett riktigt datum Kalender scheduledDate = Calendar.getInstance (); // Ange ett framtida datum med tanke på fördröjningen i sekunder definiera // Vi använder den här metoden för att underlätta testningen. // det kan göras med ett användardefinierat datum även scheduledDate.set (scheduledDate.get (Calendar.YEAR), scheduledDate.get (Calendar.MONTH), scheduledDate.get (Kalender.DAY_OF_MONTH), scheduledDate.get (Kalender.HOUR_OF_DAY ), scheduledDate.get (Calendar.MINUTE), scheduledDate.get (Calendar.SECOND) + delaySeconds); Log.d (TAG, "toastAtTime (): scheduling at -" + scheduledDate.toString ()); long scheduled = calculateUptimeMillis (scheduledDate); // posting Runnable vid bestämd tid postHandler.postAtTime (new Runnable () @Override public void run () if (callback! = null) callback.get (). showToast ("Toast kallas med" postAtTime () ". ");, schemalagt); / ** * Beräknar @link SystemClock # uptimeMillis () till * ett givet kalenderdatum. * / Private Long CalculateUptimeMillis (Kalender kalender) long time = calendar.getTimeInMillis (); long currentTime = Calendar.getInstance (). getTimeInMillis (); long diff = time - currentTime; returnera SystemClock.uptimeMillis () + diff;
public class RunnableActivity utökar Aktivitetsverktyg WorkerThread.Callback / ** * Återuppringning från @link WorkerThread * Använder @link #runOnUiThread (Runnable) för att illustrera * en sådan metod * / @Override public void showToast (final string msg) Log.d (TAG, "showToast (" + msg + ")"); runOnUiThread (new Runnable () @Override public void run () Toast.makeText (getApplicationContext (), msg, Toast.LENGTH_LONG) .show (););
MessageActivity
& WorkerThread
Låt oss sedan undersöka olika sätt att använda MessageActivity
att skicka och bearbeta Meddelande
objekt. De MessageActivity
instansierar WorkerThread
, passerar a Hanterare
som en parameter. De WorkerThread
har några offentliga metoder med uppgifter som ska ringas av aktiviteten för att ladda ner en bitmapp, ladda ner en slumpmässig bitmapp eller visa en Rostat bröd
efter en viss försenad tid. Resultaten av alla dessa verksamheter skickas tillbaka till MessageActivity
använder sig av Meddelande
objekt som skickas av responseHandler
.
MessageActivity
Som i RunnableActivity
, i MessageActivity
vi måste inställa och initiera en WorkerThread
skicka en Hanterare
att ta emot data från bakgrundsgängan. Men den här gången kommer vi inte att genomföra WorkerThread.Callback
; istället får vi svar från WorkerThread
uteslutande av Meddelande
objekt.
Sedan de flesta av MessageActivity
och RunnableActivity
koden är i princip densamma, vi koncentrerar oss bara på uiHandler
förberedelse, som kommer att skickas till WorkerThread
att ta emot meddelanden från den.
Först låt oss ge några int
nycklar som ska användas som identifierare till meddelandeobjekten.
public class MessageActivity utökar aktivitet // Meddelandekod som används i Message.what () -fältet offentliga statiska slutliga int KEY_MSG_IMAGE = 2; offentlig statisk slutlig int KEY_MSG_PROGRESS = 3; offentlig statisk slutlig int KEY_MSG_TOAST = 4;
På message
genomförandet måste vi förlänga Hanterare
och implementera handleMessage (Message)
metod där alla meddelanden kommer att behandlas. Observera att vi hämtar Message.what
för att identifiera meddelandet, och vi får också olika typer av data från Message.obj
. Låt oss snabbt granska det viktigaste Meddelande
egenskaper innan du dyker in i koden.
Message.what
: int
identifiera Meddelande
Message.arg1
: int
godtyckligt argumentMessage.arg2
: int
godtyckligt argumentMessage.obj
: Objekt
att lagra olika typer av datapublic class MessageActivity utökar aktivitet / ** * Handler ansvarig för att hantera kommunikation * från @link WorkerThread. Den skickar meddelanden * tillbaka till @link MessageActivity och hanterar * de meddelandena * / public class MessageHandler utökar hanteraren @Override public void handleMessage (Meddelande msg) switch (msg.what) // hantera bildfallet KEY_MSG_IMAGE: Bitmapp bmp = (Bitmapp) msg.obj; myImage.setImageBitmap (BMP); ha sönder; // hantera framstegBar samtal fall KEY_MSG_PROGRESS: if ((boolean) msg.obj) progressBar.setVisibility (View.VISIBLE); annars progressBar.setVisibility (View.GONE); ha sönder; // hantera toast skickat med ett meddelande för fördröjning av meddelande KEY_MSG_TOAST: String msgText = (String) msg.obj; Toast.makeText (getApplicationContext (), msgText, Toast.LENGTH_LONG) .show (); ha sönder; // Handler som tillåter kommunikation mellan // WorkerThread och den aktivitetsskyddade MessageHandler uiHandler;
Låt oss nu komma tillbaka till WorkerThread
klass. Vi lägger till en kod för att ladda ner en specifik bitmapp och även kod för att ladda ner en slumpmässig. För att utföra dessa uppgifter skickar vi Meddelande
föremål från WorkerThread
till sig själv och skicka resultaten tillbaka till MessageActivity
använder exakt samma logik som tidigare tillämpats för RunnableActivity
.
Först måste vi förlänga Hanterare
att bearbeta de nedladdade meddelandena.
offentlig klass WorkerThread utökar HandlerThread // skicka och behandla nedladdning Meddelanden på WorkerThread privat HandlerMsgImgDownloader handlerMsgImgDownloader; / ** * Nycklar för att identifiera nycklarna till @link Message # what * från meddelanden som skickas av @link #handlerMsgImgDownloader * / privat slutlig int MSG_DOWNLOAD_IMG = 0; // msg som hämtar en enda IMG privat slutlig int MSG_DOWNLOAD_RANDOM_IMG = 1; // msg som hämtar slumpmässigt img / ** * Handler ansvarig för hanteringen av bildnedladdning * Den skickar och hanterar meddelanden som identifierar sedan med * @link Message # what * @link #MSG_DOWNLOAD_IMG: enstaka bild * @link #MSG_DOWNLOAD_RANDOM_IMG: slumpmässig bild * / privat klass HandlerMsgImgDownloader utökar Handler privat HandlerMsgImgDownloader (Looper Looper) super (looper); @Override public void handleMessage (Meddelande msg) showProgressMSG (true); switch (msg.what) case MSG_DOWNLOAD_IMG: // tar emot en enda webbadress och laddar ner den String url = (String) msg.obj; downloadImageMSG (url); ha sönder; fallet MSG_DOWNLOAD_RANDOM_IMG: // tar emot en sträng [] med flera webbadresser // hämtar en bild slumpmässigt String [] urls = (String []) msg.obj; Slumpmässig slumpmässig = ny slumpmässig (); String url = url [random.nextInt (urls.length)]; downloadImageMSG (url); showProgressMSG (false);
De downloadImageMSG (String url)
Metoden är i grunden densamma som downloadImage (String url)
metod. Den enda skillnaden är att den första skickar den nedladdade bitmappen tillbaka till användargränssnittet genom att skicka ett meddelande med hjälp av responseHandler
.
offentlig klass WorkerThread utökar HandlerThread / ** * Ladda ner en bitmapp med dess webbadress och * visa den till användargränssnittet. * Den enda skillnaden med @link #downloadImage (String) * är att den skickar bilden tillbaka till användargränssnittet * med ett meddelande * / privat tomt downloadImageMSG (String urlStr) // Skapa en anslutning HttpURLConnection connection = null; prova URL url = ny URL (urlStr); anslutning = (HttpURLConnection) url.openConnection (); // få strömmen från url InputStream in = nya BufferedInputStream (connection.getInputStream ()); Final Bitmap bitmap = BitmapFactory.decodeStream (in); om (bitmapp! = null) // skicka bitmappen nerladdad och en återkoppling till UI loadImageOnUIMSG (bitmapp); fånga (IOException e) e.printStackTrace (); äntligen om (anslutning! = null) connection.disconnect ();
De loadImageOnUIMSG (Bitmap-bild)
ansvarar för att skicka ett meddelande med den nedladdade bitmappen till MessageActivity
.
/ ** * skickar en bitmapp till ui * skickar ett meddelande till @link #responseHandler * / privat void loadImageOnUIMSG (slutlig bitmappsbild) if (checkResponse ()) sendMsgToUI (responseHandler.get (). MessageActivity.KEY_MSG_IMAGE, bild)); / ** * Visa / Dölj progressBar på användargränssnittet. * Den använder @link #responseHandler till * skicka ett meddelande på UI * / privat void showProgressMSG (boolean show) Log.d (TAG, "showProgressMSG ()"); om (checkResponse ()) sendMsgToUI (responseHandler.get (). obtainMessage (MessageActivity.KEY_MSG_PROGRESS, visa));
Observera att istället för att skapa en Meddelande
objekt från början, vi använder Handler.obtainMessage (int vad, Objekt obj)
metod för att hämta a Meddelande
från den globala poolen, spara några resurser. Det är också viktigt att notera att vi ringer till obtainMessage ()
på responseHandler
, få en Meddelande
associerad med MessageActivity
's Looper
. Det finns två sätt att hämta en Meddelande
från den globala poolen: Message.obtain ()
och Handler.obtainMessage ()
.
Det enda som kvarstår vid uppladdning av uppgiften är att ge metoderna att skicka en Meddelande
till WorkerThread
för att starta nedladdningsprocessen. Observera att den här gången vi ringer Message.obtain (Handler handler, int vad, Objekt obj)
på handlerMsgImgDownloader
, associera meddelandet med WorkerThread
s looper.
/ ** * skickar ett meddelande till den aktuella tråden * med hjälp av @link #handlerMsgImgDownloader * för att ladda ner en enda bild. * / public void downloadWithMessage () Log.d (TAG, "downloadWithMessage ()"); showOperationOnUIMSG ("Skickar meddelande ..."); om (handlerMsgImgDownloader == null) handlerMsgImgDownloader = ny HandlerMsgImgDownloader (getLooper ()); Message message = Message.obtain (handlerMsgImgDownloader, MSG_DOWNLOAD_IMG, imageBUrl); handlerMsgImgDownloader.sendMessage (message); / ** * skickar ett meddelande till den aktuella tråden * med hjälp av @link #handlerMsgImgDownloader * för att ladda ner en slumpmässig bild. * / public void downloadRandomWithMessage () Log.d (TAG, "downloadRandomWithMessage ()"); showOperationOnUIMSG ("Skickar meddelande ..."); om (handlerMsgImgDownloader == null) handlerMsgImgDownloader = ny HandlerMsgImgDownloader (getLooper ()); Message message = Message.obtain (handlerMsgImgDownloader, MSG_DOWNLOAD_RANDOM_IMG, imagesUrls); handlerMsgImgDownloader.sendMessage (message);
En annan intressant möjlighet är att skicka Meddelande
objekt som ska behandlas vid ett senare tillfälle med kommandot Message.sendMessageDelayed (Message msg, long timeMillis)
.
/ ** * Visa en skål efter en försenad tid. * * Skicka ett meddelande med försenad tid på WorkerThread * och skicka ett nytt meddelande till @link MessageActivity * med en text efter att meddelandet har behandlats * / public void startMessageDelay () // meddelandefördröjning lång fördröjning = 5000; String msgText = "Hej från WorkerThread!"; // Handler ansvarig för att skicka meddelande till WorkerThread // med hjälp av Handler.Callback () för att undvika behovet av att förlänga Handler-klassen Handler handler = ny Handler (new Handler.Callback () @Override public boolean handleMessage (Message msg) responseHandler .get (). sendMessage (responseHandler.get (). obtainMessage (MessageActivity.KEY_MSG_TOAST, msg.obj)); returnera true;); // skicka meddelandehandler.sendMessageDelayed (handler.obtainMessage (0, msgText), delay);
Vi skapade en Hanterare
uttryckligen för att skicka det försenade meddelandet. Istället för att förlänga Hanterare
klass tog vi vägen att instansera a Hanterare
genom att använda Handler.Callback
gränssnitt, för vilket vi genomförde handleMessage (Message msg)
Metod för att bearbeta fördröjda Meddelande
.
Du har nu sett tillräckligt med kod för att förstå hur man tillämpar de grundläggande HaMeR-ramkoncepten för att hantera samtidighet på Android. Det finns några andra intressanta funktioner i det slutliga projektet som lagras på GitHub, och jag rekommenderar starkt att du kolla in det.
Slutligen har jag några sista överväganden som du bör tänka på:
RetainedFragment
att lagra tråden och fylla i bakgrundsgängan med aktivitetens referens varje gång aktiviteten förstörs. Ta en titt på lösningen i det slutliga projektet på GitHub.Runnable
och Meddelande
föremål bearbetade på Handlar
kör inte asynkront. De körs synkront på tråden som är associerad med hanteraren. För att göra den asynkron måste du skapa en annan tråd, skicka / skicka den Meddelande
/Runnable
objekt på det och ta emot resultaten vid rätt tidpunkt.Som du kan se har HaMeR-ramen många olika möjligheter, och det är en ganska öppen lösning med många alternativ för hantering av samtidighet på Android. Dessa egenskaper kan vara fördelar över AsyncTask
, beroende på dina behov. Utforska mer av ramverket och läs dokumentationen, och du kommer att skapa bra saker med det.
Ses snart!