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.
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:
kjørbart
en annen for Budskap
samtalerHandlerThread
objekter:WorkerThread
å motta og behandle samtaler fra brukergrensesnittetCounterThread
å motta Budskap
samtaler fra WorkerThread
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
.
På 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 ();
WorkerThread
og tilbakekoblingsgrensesnittetDe 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 WeakReferenceresponseHandler; // 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);
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 ());
På 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
Handler.post ()
på WorkerThreadDe 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:
.post()
blir kalt en handler tilknyttet trådens looper..post()
blir kalt en Handler tilknyttet andre trådens looper. WorkerThread.downloadWithRunnable ()
Metode innlegg a kjørbart
til WorkerThread
's MessageQueue
bruker postHandler
, en handler
assosiert med WorkThread
's Looper
.bitmap
på WorkerThread
.responseHandler
, en håndterer tilknyttet brukergrensesnittet, brukes til å legge inn en runnable på RunnableActivity
inneholder bitmappen.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;
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 (););
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
.
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;
På 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.what
: int
identifisere Budskap
Message.arg1
: int
vilkårlig argumentasjonMessage.arg2
: int
vilkårlig argumentasjonMessage.obj
: Gjenstand
å lagre ulike typer dataoffentlig 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;
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 ()
på 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 WorkerThread
s 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
.
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å:
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.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!