Praktisk samtidighet på Android med HaMeR

I forståelse av samtidighet på Android Ved hjelp av HaMeR snakket vi om grunnleggende om HaMeR (handler, Budskap, og kjørbart) rammeverk. Vi dekket valgene, samt når og hvordan du bruker den. 

I dag lager vi et enkelt program for å utforske begrepene som er lært. Med en praktisk tilnærming ser vi hvordan du bruker de forskjellige mulighetene til HaMeR ved å administrere samstemmighet på Android.

1. Prøveapplikasjonen

La oss komme til jobb og legge inn noen kjørbart og send Budskap objekter på en prøveapplikasjon. For å holde det så enkelt som mulig, vil vi bare utforske de mest interessante delene. Alle ressursfiler og standardaktivitetssamtaler vil bli ignorert her. Så jeg anbefaler på det sterkeste at du sjekker ut kildekoden til prøveapplikasjonen med sine omfattende kommentarer. 

Appen vil bestå av:

  • To aktiviteter, en for kjørbart en annen for Budskap samtaler
  • To HandlerThread objekter:
    • WorkerThread å motta og behandle samtaler fra brukergrensesnittet
    • CounterThread å motta Budskap samtaler fra WorkerThread
  • Noen verktøysklasser (for å bevare objekter under konfigurasjonsendringer og for layout)

2. Posting og mottak av runnables

La oss begynne å eksperimentere med Handler.post (kjørbart) metode og dets variasjoner, som legger til en runnable til a MessageQueue forbundet med en tråd. Vi oppretter en aktivitet som kalles RunnableActivity, som kommuniserer med en bakgrunnstråd kalt WorkerThread.

De RunnableActivity instantierer en bakgrunnstråd kalt WorkerThread, passerer en handler og a WorkerThread.Callback som parametere. Aktiviteten kan ringe på WorkerThread å asynkront laste ned en bitmap og vise en skål på et bestemt tidspunkt. Resultatene av oppgavene som er utført av arbeidstråden, sendes til RunnableActivity av runnables postet på handler mottatt av WorkerThread.

2.1 Forberede en handler for RunnableActivity

RunnableActivity vi lager en handler å bli sendt til WorkerThread. De uiHandler vil bli assosiert med Looper fra brukergrensesnittet, siden det blir kalt fra den tråden.

offentlig klasse RunnableActivity utvider Aktivitet // Handler som tillater kommunikasjon mellom // WorkerThread og aktivitetsbeskyttet Handler uiHandler; @Override protected void onCreate (Bundle savedInstanceState) super.onCreate (savedInstanceState); setContentView (R.layout.activity_main); // forberede brukergrensesnittet til å sende til WorkerThread uiHandler = new Handler (); 

2.2 Deklarere WorkerThread og tilbakekoblingsgrensesnittet

De WorkerThread er en bakgrunnstråd hvor vi starter forskjellige typer oppgaver. Den kommuniserer med brukergrensesnittet ved hjelp av responseHandler og et tilbakeringingsgrensesnitt mottatt under sin instantiering. Referansene fra aktivitetene er WeakReference <> skriv, siden en aktivitet kunne bli ødelagt og referansen mistet.

Klassen tilbyr et grensesnitt som kan implementeres av brukergrensesnittet. Det strekker seg også HandlerThread, en hjelperklasse bygget på toppen av Tråd som allerede inneholder a Looper, og a MessageQueue. Derfor har den den rette oppsettå bruke HaMeR-rammeverket.

offentlig klasse WorkerThread utvider HandlerThread / ** * Grensesnitt for å lette samtaler på brukergrensesnittet. * / offentlig grensesnitt Tilbakeringing void loadImage (Bitmap image); void showToast (String msg);  // Denne Handler er bare ansvarlig // for å legge inn Runnables på denne Thread Private Handler postHandler; // Handler er mottatt fra MessageActivity og RunnableActivity // ansvarlig for mottak av Runnable-samtaler som skal behandles // på brukergrensesnittet. Tilbakekallingen vil hjelpe denne prosessen. privat WeakReference responseHandler; // Tilbakering fra UI // det er en WeakReference fordi den kan ugyldiggjøres // under "konfigurasjonsendringer" og andre hendelser privat WeakReference Ring tilbake; privat siste String imageAUrl = "https://pixabay.com/static/uploads/photo/2016/08/05/18/28/mobile-phone-1572901_960_720.jpg"; / ** * Konstruktøren mottar en Handler og en tilbakeringing fra brukergrensesnittet * @param responseHandler ansvarlig for å sende Runnable til brukergrensesnittet * @param tilbakeringing fungerer sammen med responshandleren * tillater samtaler direkte på brukergrensesnittet * / offentlig WorkerThread (HandlerThread responseHandler, Tilbakering tilbakeringing) super (TAG); this.responseHandler = new WeakReference <> (responseHandler); this.callback = new WeakReference <> (tilbakeringing); 

2.3 Initialisering WorkerThread

Vi må legge til en metode til WorkerThread å bli kalt av aktivitetene som forbereder tråden postHandler for bruk. Metoden må bare kalles etter at tråden er startet.

offentlig klasse WorkerThread utvider HandlerThread / ** * Forbered PostHandler. * Det må kalles etter at tråden har startet * / public void prepareHandler () postHandler = new Handler (getLooper ()); 

RunnableActivity vi må gjennomføre WorkerThread.Callback og initier tråden slik at den kan brukes.

offentlig klasse RunnableActivity utvider Aktivitetsredskaper WorkerThread.Callback // BackgroundThread ansvarlig for nedlasting av den bildebeskyttede WorkerThread workerThread; / ** * Initialiser @link WorkerThread forekomsten * bare hvis den ikke er initialisert ennå. * / public void initWorkerThread () if (workerThread == null) workerThread = new WorkerThread (uiHandler, dette); workerThread.start (); workerThread.prepareHandler ();  / ** * angi bildet lastet ned på bg-tråden til imageView * / @Override public void loadImage (Bitmap image) myImage.setImageBitmap (image);  @Override public void showToast (final string msg) // for å bli implementert

2.4 Bruke Handler.post () på WorkerThread

De WorkerThread.downloadWithRunnable () Metoden laster ned en bitmap og sender den til RunnableActivity å bli vist i et bildevisning. Det illustrerer to grunnleggende bruksområder av Handler.post (Runnable Run) kommando:

  • For å tillate en tråd å legge inn et Runnable-objekt til en MessageQueue knyttet til seg selv når .post() blir kalt en handler tilknyttet trådens looper.
  • For å tillate kommunikasjon med andre tråder, når .post() blir kalt en Handler tilknyttet andre trådens looper.
  1. De WorkerThread.downloadWithRunnable () Metode innlegg a kjørbart til WorkerThread's MessageQueue bruker postHandler, en handler assosiert med WorkThread's Looper.
  2. Når runnable er behandlet, laster den ned a bitmapWorkerThread.
  3. Etter at bitmapet er lastet ned, er det responseHandler, en håndterer tilknyttet brukergrensesnittet, brukes til å legge inn en runnable på RunnableActivity inneholder bitmappen.
  4. Runnable er behandlet, og WorkerThread.Callback.loadImage brukes til å vise det nedlastede bildet på en Imageview.
offentlig klasse WorkerThread utvider HandlerThread / ** * post en Runnable til WorkerThread * Last ned en bitmap og sender bildet * til brukergrensesnittet @link RunnableActivity * ved hjelp av @link #responseHandler med * hjelp fra @link #callback * / offentlig ugyldig nedlastingWithRunnable () // post Runnable to WorkerThread postHandler.post (ny Runnable () @Override public void run () prøv // sover i 2 sekunder for å etterligne langvarig drift TimeUnit.SECONDS .sleep (2); // Last ned bilde og send til UI downloadImage (imageAUrl); catch (InterruptedException e) e.printStackTrace (););  / ** * Last ned en bitmap med sin URL og * send til brukergrensesnittet bildet lastet ned * / privat tomt nedlastingsbilde (String urlStr) // Opprett en tilkobling HttpURLConnection connection = null; prøv URL url = ny URL (urlStr); forbindelse = (HttpURLConnection) url.openConnection (); // få strømmen fra url InputStream in = ny BufferedInputStream (connection.getInputStream ()); Endelig Bitmap bitmap = BitmapFactory.decodeStream (inn); hvis (bitmap! = null) // send bitmappen nedlastet og en tilbakemelding til brukergrensesnittet loadImageOnUI (bitmap);  else  catch (IOException e) e.printStackTrace ();  endelig hvis (forbindelse! = null) connection.disconnect ();  / ** * sender en bitmap til ui * poster en Runnable til @link #responseHandler * og bruker @link Tilbakeringing * / privat tomtlastImageOnUI (endelig Bitmap-bilde) Log.d (TAG, "loadImageOnUI (" + bilde + ")"); hvis (checkResponse ()) responseHandler.get (). post (ny Runnable () @Override public void run () callback.get () .loadImage (bilde););  // verifisere om responseHandler er tilgjengelig // hvis ikke aktiviteten går forbi noen ødeleggelse hendelsen privat boolean checkResponse () return responseHandler.get ()! = null; 

2.5 Bruke Handler.postAtTime () og Activity.runOnUiThread ()

De WorkerThread.toastAtTime ()planlegger en oppgave som skal utføres på et bestemt tidspunkt, og viser en Skål til brukeren. Metoden illustrerer bruken av Handler.postAtTime () og Activity.runOnUiThread ().

  • Handler.postAtTime (Runnable run, lang oppetidMillis) posterer en runnable på et gitt tidspunkt.
  • Activity.runOnUiThread (Runnable run) bruker standardbrukerens brukerhåndterer til å legge inn en runnable til hovedtråden.
offentlig klasse WorkerThread utvider HandlerThread / ** * viser en toast på brukergrensesnittet. * Planlegger oppgaven med tanke på gjeldende tid. * Det kan være planlagt når som helst, vi bruker * 5 sekunder for å forenkle debugging * / public void toastAtTime () Log.d (TAG, "toastAtTime (): current -" + Calendar.getInstance (). ToString ()); // sekunder å legge til på nåværende tidspunkt int delaySeconds = 5; // testing ved hjelp av en ekte dato Kalender scheduledDate = Calendar.getInstance (); // sette en fremtidig dato med tanke på forsinkelsen i sekunder definere // vi bruker denne tilnærmingen bare for å lette testingen. // det kan gjøres ved hjelp av en brukerdefinert dato også 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 (): planlegging på -" + scheduledDate.toString ()); lang planlagt = calculateUptimeMillis (scheduledDate); // posting Runnable på bestemt tidspunkt postHandler.postAtTime (new Runnable () @Override public void run () if (callback! = null) callback.get (). showToast ("Toast som heter" postAtTime () ". ");, planlagt);  / ** * Beregner @link SystemClock # uptimeMillis () til * en gitt kalenderdato. * / Private long calculateUptimeMillis (Kalenderkalender) lang tid = calendar.getTimeInMillis (); long currentTime = Calendar.getInstance (). getTimeInMillis (); long diff = time - currentTime; returner SystemClock.uptimeMillis () + diff; 
offentlig klasse RunnableActivity utvider Aktivitetsredskaper WorkerThread.Callback / ** * Tilbakering fra @link WorkerThread * Bruker @link #runOnUiThread (Runnable) for å illustrere * en slik metode * / @Overtrid offentlig tomgangstastToast (siste strengt msg)  Log.d (TAG, "showToast (" + msg + ")"); runOnUiThread (new Runnable () @Override public void run () Toast.makeText (getApplicationContext (), msg, Toast.LENGTH_LONG) .show ();); 

3. Sende meldinger med MessageActivity & WorkerThread

Neste, la oss utforske noen forskjellige måter å bruke MessageActivity  å sende og behandle Budskap objekter. De MessageActivity instantiates WorkerThread, passerer en handler som en parameter. De WorkerThread har noen offentlige metoder med oppgaver å bli kalt av aktiviteten for å laste ned en bitmap, laste ned en tilfeldig bitmap, eller vise en Skål etter litt forsinket tid. Resultatene av alle disse operasjonene sendes tilbake til MessageActivity ved hjelp av Budskap objekter sendt av responseHandler.

3.1 Klargjøre Response Handler Fra MessageActivity

Som i RunnableActivity, i MessageActivity vi må instantiere og initialisere en WorkerThread sender en handler å motta data fra bakgrunns tråden. Men denne gangen vil vi ikke implementere WorkerThread.Callback; I stedet mottar vi svar fra WorkerThread utelukkende av Budskap objekter.

Siden de fleste av MessageActivity og RunnableActivity koden er i utgangspunktet den samme, vi vil konsentrere oss bare på uiHandler forberedelse, som vil bli sendt til WorkerThread å motta meldinger fra den.

Først, la oss gi noen int nøkler som skal brukes som identifikatorer til meldingsobjektene.

offentlig klasse MessageActivity utvider Aktivitet // Meldingsidentifikator som brukes på Message.what () feltet offentlig statisk endelig int KEY_MSG_IMAGE = 2; offentlig statisk endelig int KEY_MSG_PROGRESS = 3; offentlig statisk endelig int KEY_MSG_TOAST = 4; 

MessageHandler implementering, må vi utvide handler og implementere handleMessage (Message) metode, der alle meldinger blir behandlet. Legg merke til at vi henter Message.what å identifisere meldingen, og vi får også forskjellige typer data fra Message.obj. La oss raskt vurdere det viktigste Budskap egenskaper før du går inn i koden.

  • Message.whatint identifisere Budskap
  • Message.arg1int vilkårlig argumentasjon
  • Message.arg2int vilkårlig argumentasjon
  • Message.objGjenstand å lagre ulike typer data
offentlig klasse MessageActivity utvider Aktivitet / ** * Handler ansvarlig for å administrere kommunikasjon * fra @link WorkerThread. Den sender meldinger * tilbake til @link MessageActivity og håndterer * disse meldingene * / public class MessageHandler utvider Handler @Override public void handleMessage (Meldingsmelding) switch (msg.what) // håndtere bilde sak KEY_MSG_IMAGE:  Bitmap bmp = (Bitmap) msg.obj; myImage.setImageBitmap (BMP); gå i stykker;  // håndtere fremgangBar samtaler sak KEY_MSG_PROGRESS: if ((boolean) msg.obj) progressBar.setVisibility (View.VISIBLE); ellers progressBar.setVisibility (View.GONE); gå i stykker;  // håndtere toast sendt med en melding forsinkelse sak KEY_MSG_TOAST: String msgText = (String) msg.obj; Toast.makeText (getApplicationContext (), msgText, Toast.LENGTH_LONG) .show (); gå i stykker;  // Handler som tillater kommunikasjon mellom // WorkerThread og aktivitetsbeskyttet MessageHandler uiHandler;  

3.2 Sende meldinger med WorkerThread

La oss nå komme tilbake til WorkerThread klasse. Vi legger til noe kode for å laste ned en bestemt bitmap og også kode for å laste ned en tilfeldig en. For å utføre disse oppgavene sender vi Budskap gjenstander fra WorkerThread til seg selv og send resultatene tilbake til MessageActivity bruker nøyaktig samme logikk som tidligere ble brukt for RunnableActivity.

Først må vi forlenge handler å behandle de nedlastede meldingene.

offentlig klasse WorkerThread utvider HandlerThread // send og behandler nedlastinger Meldinger på WorkerThread privat HandlerMsgImgDownloader handlerMsgImgDownloader; / ** * Nøkler for å identifisere nøklene til @link Message # what * fra Meldinger sendt av @link #handlerMsgImgDownloader * / privat endelig int MSG_DOWNLOAD_IMG = 0; // msg som laster ned en enkelt IMG privat endelig int MSG_DOWNLOAD_RANDOM_IMG = 1; // msg som laster ned tilfeldig img / ** * Handler ansvarlig for å håndtere bildenedlastingen * Den sender og håndterer Meldinger som identifiserer ved bruk av @link Message # what * @link #MSG_DOWNLOAD_IMG: enkeltbilde * @link #MSG_DOWNLOAD_RANDOM_IMG: tilfeldig bilde * / privat klasse HandlerMsgImgDownloader utvider Handler privat HandlerMsgImgDownloader (Looper looper) super (looper);  @Override public void handleMessage (Message msg) showProgressMSG (true); bytt (msg.what) tilfelle MSG_DOWNLOAD_IMG: // mottar en enkelt url og laster den ned String url = (String) msg.obj; downloadImageMSG (url); gå i stykker;  tilfelle MSG_DOWNLOAD_RANDOM_IMG: // mottar en streng [] med flere url // laster et bilde tilfeldig String [] urls = (String []) msg.obj; Tilfeldig tilfeldig = Ny Tilfeldig (); String url = urls [random.nextInt (urls.length)]; downloadImageMSG (url);  showProgressMSG (false); 

De downloadImageMSG (String url) Metoden er i utgangspunktet den samme som downloadImage (String url) metode. Den eneste forskjellen er at den første sender den nedlastede bitmappen tilbake til brukergrensesnittet ved å sende en melding ved hjelp av responseHandler.

offentlig klasse WorkerThread utvider HandlerThread / ** * Last ned en bitmap med sin URL og * vis den til brukergrensesnittet. * Den eneste forskjellen med @link #downloadImage (String) * er at den sender bildet tilbake til brukergrensesnittet * ved hjelp av en melding * / privat ugyldig downloadImageMSG (String urlStr) // Opprett en tilkobling HttpURLConnection connection = null; prøv URL url = ny URL (urlStr); forbindelse = (HttpURLConnection) url.openConnection (); // få strømmen fra url InputStream in = ny BufferedInputStream (connection.getInputStream ()); Endelig Bitmap bitmap = BitmapFactory.decodeStream (inn); hvis (bitmap! = null) // send bitmappen nedlastet og en tilbakemelding til UI loadImageOnUIMSG (bitmap);  fangst (IOException e) e.printStackTrace ();  endelig hvis (forbindelse! = null) connection.disconnect (); 

De loadImageOnUIMSG (Bitmap-bilde) er ansvarlig for å sende en melding med den nedlastede bitmappen til MessageActivity

 / ** * sender en bitmap til ui * sender en melding til @link #responseHandler * / privat ugyldiglastImageOnUIMSG (endelig bitmapbilde) if (checkResponse ()) sendMsgToUI (responseHandler.get (). MessageActivity.KEY_MSG_IMAGE, bilde));  / ** * Vis / skjul fremgangBar på brukergrensesnittet. * Den bruker @link #responseHandler til * sende en melding på brukergrensesnittet * / privat ugyldig showProgressMSG (boolean show) Log.d (TAG, "showProgressMSG ()"); hvis (checkResponse ()) sendMsgToUI (responseHandler.get (). obtainMessage (MessageActivity.KEY_MSG_PROGRESS, show)); 

Legg merke til at i stedet for å lage en Budskap objekt fra bunnen av, vi bruker Handler.obtainMessage (int hva, Objekt obj) metode for å hente a Budskap fra det globale bassenget, sparer noen ressurser. Det er også viktig å merke seg at vi ringer til obtainMessage ()responseHandler, skaffe en Budskap assosiert med MessageActivity's Looper. Det er to måter å hente a Budskap fra det globale bassenget: Message.obtain () og Handler.obtainMessage ().

Det eneste som gjenstår å gjøre på nedlastingsoppgaven for bildet, er å gi metodene til å sende en Budskap til WorkerThread for å starte nedlastingsprosessen. Legg merke til at denne gangen vi ringer Message.obtain (Handler handler, int hva, Objekt obj) på handlerMsgImgDownloader, forbinder meldingen med WorkerThreads looper.

 / ** * sender en melding til gjeldende tråd * ved hjelp av @link #handlerMsgImgDownloader * for å laste ned et enkelt bilde. * / offentlig ugyldig downloadWithMessage () Log.d (TAG, "downloadWithMessage ()"); showOperationOnUIMSG ("Sende melding ..."); hvis (handlerMsgImgDownloader == null) handlerMsgImgDownloader = ny HandlerMsgImgDownloader (getLooper ()); Meldingsmelding = Message.obtain (handlerMsgImgDownloader, MSG_DOWNLOAD_IMG, imageBUrl); handlerMsgImgDownloader.sendMessage (melding);  / ** * sender en melding til gjeldende tråd * ved hjelp av @link #handlerMsgImgDownloader * for å laste ned et tilfeldig bilde. * / offentlig ugyldig nedlastingRandomWithMessage () Log.d (TAG, "downloadRandomWithMessage ()"); showOperationOnUIMSG ("Sende melding ..."); hvis (handlerMsgImgDownloader == null) handlerMsgImgDownloader = ny HandlerMsgImgDownloader (getLooper ()); Meldingsmelding = Message.obtain (handlerMsgImgDownloader, MSG_DOWNLOAD_RANDOM_IMG, imagesUrls); handlerMsgImgDownloader.sendMessage (melding); 

En annen interessant mulighet er å sende Budskap objekter som skal behandles på et senere tidspunkt med kommandoen Message.sendMessageDelayed (Meldingsmelding, lang tidMillis).

/ ** * Vis en skål etter en forsinket tid. * * send en melding med forsinket tid på WorkerThread * og sender en ny melding til @link MessageActivity * med en tekst etter at meldingen er behandlet * / public void startMessageDelay () // melding forsinkelse lang forsinkelse = 5000; String msgText = "Hei fra WorkerThread!"; // Handler ansvarlig for å sende Melding til WorkerThread // ved hjelp av Handler.Callback () for å unngå behovet for å utvide Handler-klassen Handler handler = ny Handler (ny Handler.Callback () @Override public boolean handleMessage (Message msg) responseHandler .get (). sendMessage (responseHandler.get (). oppnåMessage (MessageActivity.KEY_MSG_TOAST, msg.obj)); return true;); // sende melding handler.sendMessageDelayed (handler.obtainMessage (0, msgText), delay); 

Vi opprettet en handler uttrykkelig for å sende den forsinkede meldingen. I stedet for å utvide handler klasse, tok vi ruten for å instansere a handler ved å bruke Handler.Callback grensesnitt, som vi implementerte handleMessage (Message msg) metode for å behandle forsinket Budskap.

4. Konklusjon

Du har sett nok kode nå, for å forstå hvordan du bruker de grunnleggende HaMeR-rammekonseptene for å administrere samtidighet på Android. Det er noen andre interessante funksjoner i det siste prosjektet som er lagret på GitHub, og jeg anbefaler på det sterkeste at du sjekker det ut. 

Til slutt har jeg noen siste hensyn som du bør huske på:

  • Ikke glem å vurdere Android-aktivitetslivssyklusen når du arbeider med HaMeR og tråder generelt. Ellers kan appen din mislykkes når tråden prøver å få tilgang til aktiviteter som er ødelagt på grunn av konfigurasjonsendringer eller av andre årsaker. En vanlig løsning er å bruke a RetainedFragment å lagre tråden og fylle bakgrunns tråden med aktivitetens referanse hver gang aktiviteten er ødelagt. Ta en titt på løsningen i det endelige prosjektet på GitHub.
  • Oppgaver som kjører på grunn av kjørbart og Budskap objekter behandlet på handlers Ikke kjør asynkront. De løper synkront på tråden assosiert med håndtereren. For å gjøre det asynkront, må du opprette en annen tråd, send / send inn Budskap/kjørbart objekt på det, og motta resultatene på riktig tidspunkt.

Som du kan se har HaMeR-rammeverket mange forskjellige muligheter, og det er en ganske åpen løsning med mange alternativer for å administrere samtidighet på Android. Disse egenskapene kan være fordeler over AsyncTask, avhengig av dine behov. Utforsk flere av rammene og les dokumentasjonen, og du vil lage gode ting med det.

Ser deg snart!