În înțelegerea coerenței pe Android folosind HaMeR, am vorbit despre elementele de bază ale HaMeR (manipulant
, Mesaj
, și runnable
) cadru. Am acoperit opțiunile sale, precum și când și cum să le folosim.
Astăzi, vom crea o aplicație simplă pentru a explora conceptele învățate. Cu o abordare hands-on, vom vedea cum să aplicăm diferitele posibilități ale HaMeR în gestionarea concurenței pe Android.
Să mergem la lucru și să postăm câteva runnable
si trimite Mesaj
obiecte pe o aplicație probă. Pentru a păstra cât mai simplu posibil, vom explora numai cele mai interesante părți. Toate fișierele de resurse și apelurile de activitate standard vor fi ignorate aici. Așadar, vă sfătuiesc cu tărie să verificați codul sursă al aplicației de probă cu comentariile sale ample.
Aplicația va consta din:
runnable
altul pentru Mesaj
apeluriHandlerThread
obiecte:WorkerThread
pentru a primi și a procesa apelurile de la interfața utilizatorCounterThread
a primi Mesaj
apeluri de la WorkerThread
Să începem să experimentăm cu Handler.post (Runnable)
metoda și variațiile ei, care adaugă un runnable la a MessageQueue
asociate cu un fir. Vom crea o activitate numită RunnableActivity
, care comunică cu un fir de fund numit WorkerThread
.
RunnableActivity
instantează un fir de fund numit WorkerThread
, trecând a manipulant
și a WorkerThread.Callback
ca parametri. Activitatea poate face apeluri WorkerThread
pentru a descărca asincron un bitmap și a prezenta un toast la un moment dat. Rezultatele sarcinilor efectuate de firul lucrătorului sunt transmise RunnableActivity
de runnables postat pe manipulant
primit de WorkerThread
.
Pe RunnableActivity
vom crea o manipulant
să fie trecut la WorkerThread
. uiHandler
va fi asociat cu maieză
de la firul UI, deoarece este chemat din acel fir.
clasa publică RunnableActivity extinde Activitatea // Handler care permite comunicarea între // WorkerThread și Handler protejat de activitate uiHandler; @Override protejate void onCreate (Bundle savedInstanceState) super.onCreate (savedInstanceState); setContentView (R.layout.activity_main); // pregătiți manipulatorul UI pentru a trimite la WorkerThread uiHandler = new Handler ();
WorkerThread
și interfața ei de apel invers WorkerThread
este un fir de fund în care vom începe diferite tipuri de sarcini. Acesta comunică cu interfața de utilizator utilizând responseHandler
și o interfață de apel primit în timpul instanțierii. Referințele primite din activități sunt WeakReference <>
tip, deoarece o activitate ar putea fi distrusă și referința pierdută.
Clasa oferă o interfață care poate fi implementată de interfața utilizator. De asemenea, se extinde HandlerThread
, o clasă de ajutor construită pe partea de sus Fir
care conține deja a maieză
, și a MessageQueue
. Prin urmare, este corect înființatsă utilizeze cadrul HaMeR.
clasa publică WorkerThread extinde HandlerThread / ** * Interfață pentru a facilita apelurile pe interfața utilizator. * / interfață publică Callback void loadImage (imagine bitmap); void showToast (mesaj șir); // Acest Handler va fi responsabil numai // pentru postarea Runnables pe acest Post Handler privat Handler postHandler; // Handler este primit de la MessageActivity și RunnableActivity // responsabil pentru primirea apelurilor Runnable care vor fi procesate // pe UI. Redirecționarea apelului va ajuta la acest proces. private WeakReferenceresponseHandler; // Callback de la UI // este o adresă WeakReference deoarece poate fi invalidată // în timpul "schimbărilor de configurare" și a altor evenimente private WeakReference suna inapoi; privat final String imageAUrl = "https://pixabay.com/static/uploads/photo/2016/08/05/18/28/mobile-phone-1572901_960_720.jpg"; / ** * Constructorul primește un Handler și un Callback din răspunsul UI * @paramHandler responsabil pentru postarea Runnable la UI * @param callback funcționează împreună cu answerHandler * permite apeluri direct pe UI * / public WorkerThread (Handler răspunsHandler, apel invers) super (TAG); this.responseHandler = noua Referință slabă <> (răspunsHandler); this.callback = noua adresă slabă <> (apel invers);
WorkerThread
Trebuie să adăugăm o metodă WorkerThread
pentru a fi numit de activitățile care pregătesc firele postHandler
pentru utilizare. Metoda trebuie apelată numai după pornirea firului.
clasa publică WorkerThread extinde HandlerThread / ** * Pregătiți postHandler. * Trebuie să fie chemat după ce firul a început * / public void prepareHandler () postHandler = Handler nou (getLooper ());
Pe RunnableActivity
trebuie să punem în aplicare WorkerThread.Callback
și inițializați firul astfel încât acesta să poată fi utilizat.
clasa publică RunnableActivity se extinde Activitatea implementează WorkerThread.Callback // BackgroundThread responsabil pentru descărcarea lucrării WorkerThread workerThread; / ** * Inițializați instanța @link WorkerThread * numai dacă nu a fost încă inițializată. * / public void initWorkerThread () dacă (workerThread == null) workerThread = nou WorkerThread (uiHandler, this); workerThread.start (); workerThread.prepareHandler (); / ** * a seta imaginea descărcată pe firul bg în imagineView * / @Override public void loadImage (imagine bitmap) myImage.setImageBitmap (imagine); @Override public void showToast (ultimul mesaj String) // să fie implementat
Handler.post ()
pe WorkerThread WorkerThread.downloadWithRunnable ()
metoda descarcă un bitmap și îl trimite RunnableActivity
pentru a fi afișate într-o imagine Vizualizare. Acesta ilustrează două utilizări de bază ale Handler.post (Runable run)
comanda:
.post()
este chemat pe un Handler asociat cu Looper's Thread..post()
este chemat pe un Handler asociat cu Looper lui Other Thread. WorkerThread.downloadWithRunnable ()
postările de metodă a runnable
la WorkerThread
„s MessageQueue
folosind postHandler
, A manipulant
asociat cu WorkThread
„s maieză
.Bitmap
pe WorkerThread
.responseHandler
, un handler asociat cu firul UI, este folosit pentru a posta un runnable pe RunnableActivity
care conține bitmap.WorkerThread.Callback.loadImage
este folosit pentru a expune imaginea descărcată pe un ImageView
.clasa publica WorkerThread extinde HandlerThread / ** * posta un Runnable la WorkerThread * Descărcați un bitmap și trimite imaginea * la UI @link RunnableActivity * folosind ajutorul @link #responseHandler cu * din @link #) = / public void downloadWithRunnable () // post Runnable la WorkerThread postHandler.post (noul Runnable () @Override public void run () try // doarme 2 secunde pentru a emula operațiunea de lungă durată TimeUnit.SECONDS .Sleep (2); // Descărcați imaginea și trimite la UI downloadImage (imageAUrl); captură (InterruptedException e) e.printStackTrace ();); / ** * Descărcați un bitmap utilizând adresa URL și trimiteți către UI imaginea descărcată * / void private downloadImage (String urlStr) // Creați o conexiune HttpURLConnection connection = null; încercați URL url = URL nou (urlStr); conexiune = (HttpURLConnection) url.openConnection (); // a obține fluxul de la url InputStream in = BufferedInputStream nou (connection.getInputStream ()); bitmap bitmap final = BitmapFactory.decodeStream (in); dacă (bitmap! = null) // trimite bitmap-ul descărcat și un feedback către UI loadImageOnUI (bitmap); altceva captură (IOException e) e.printStackTrace (); în cele din urmă if (connection! = null) connection.disconnect (); / ** * trimite un bitmap la ui * postând un Runnable la @link #responseHandler * și folosind @ Callback * / void private loadImageOnUI (imaginea Bitmap finală) Log.d (TAG, "loadImageOnUI (" + imagine + ")"); dacă (checkResponse ()) responseHandler.get () .post (noul Runnable () @Override public void run () callback.get () loadImage (imagine); // verifica dacă răspunsulHandler este disponibil // dacă nu Activitatea trece prin niște evenimente de distrugere private boolean checkResponse () return responseHandler.get ()! = null;
Handler.postAtTime ()
și Activity.runOnUiThread ()
WorkerThread.toastAtTime ()
planifică o sarcină care trebuie executată la un anumit moment, prezentând a Paine prajita
către utilizator. Metoda ilustrează utilizarea Handler.postAtTime ()
si Activity.runOnUiThread ()
.
Handler.postAtTime (runnable run, longtimetimeMillis)
postări un runnable la un moment dat.Activity.runOnUiThread (Runable run)
utilizează modulul de tratare a interfeței implicite pentru a posta o alertă la firul principal.clasa publica WorkerThread extinde HandlerThread / ** * arată un toast pe interfața de utilizare. * planifică sarcina ținând cont de ora curentă. * Aceasta ar putea fi programată oricând, vom folosi * 5 secunde pentru a facilita depanarea * / public void toastAtTime () Log.d (TAG, "toastAtTime (): curent -" + Calendar.getInstance () ()); // secunde pentru a adăuga la ora curentă înt delaySeconds = 5; // testarea folosind o dată reală Calendar scheduledDate = Calendar.getInstance (); // setarea unei date viitoare luând în considerare întârzierea în secunde define // folosim această abordare doar pentru a facilita testarea. // ar putea fi făcută folosind o dată definită de utilizator, de asemenea, programateDate.set (programateDate.get (Calendar.YEAR), scheduledDate.get (Calendar.MONTH), scheduledDate.get (Calendar.DAY_OF_MONTH), scheduledDate.get (Calendar.HOUR_OF_DAY ), programateDate.get (Calendar.MINUTE), programateDate.get (Calendar.SECOND) + delaySeconds); Log.d (TAG, "toastAtTime (): programare la -" + scheduledDate.toString ()); lung programat = calculUptimeMillis (programatData); // postarea Runnable la un moment dat postHandler.postAtTime (new Runnable () @Override public void run () if (callback! = null) callback.get () showToast ("Toast numit folosind 'postAtTime ()'. ");, programată); / ** * Calculează @link SystemClock # uptimeMillis () pentru * la o dată calendaristică dată. * / private long calculUptimeMillis (Calendar) long time = calendar.getTimeInMillis (); lungul curentTime = Calendar.getInstance (). getTimeInMillis (); lung diff = time - currentTime; returnați SystemClock.uptimeMillis () + diff;
clasa publică RunnableActivity se extinde Activitatea implementează WorkerThread.Callback / ** * Callback from @link WorkerThread * Utilizează @link #runOnUiThread (Runnable) pentru a ilustra * o astfel de metodă * / @Override public void showToast final String msg) Log.d (TAG, "showToast (" + msg + ")"); runOnUiThread (noul Runnable () @Override public void run () Toast.makeText (getApplicationContext (), msg, Toast.LENGTH_LONG) .show (););
MessageActivity
& WorkerThread
Apoi, să explorăm câteva moduri diferite de utilizare MessageActivity
pentru a trimite și procesa Mesaj
obiecte. MessageActivity
instanțiază WorkerThread
, trecând a manipulant
ca parametru. WorkerThread
are câteva metode publice cu sarcini care trebuie să fie apelate de către activitate pentru a descărca un bitmap, pentru a descărca un bitmap aleator sau pentru a expune o imagine Paine prajita
după un timp amânat. Rezultatele tuturor acestor operațiuni sunt trimise înapoi la MessageActivity
utilizând Mesaj
obiecte trimise de responseHandler
.
MessageActivity
Ca și în RunnableActivity
, în MessageActivity
va trebui să instanțiăm și să inițializăm o WorkerThread
trimiterea unui mesaj manipulant
pentru a primi date din firul de fundal. Cu toate acestea, de data aceasta nu vom implementa WorkerThread.Callback
; în schimb, vom primi răspunsuri de la WorkerThread
exclusiv prin Mesaj
obiecte.
Deoarece majoritatea MessageActivity
și RunnableActivity
codul este în esență același, ne vom concentra doar pe uiHandler
care va fi trimis la WorkerThread
pentru a primi mesaje de la acesta.
În primul rând, să oferim câteva int
chei pentru a fi utilizate ca identificatori pentru obiectele Message.
clasa publica MessageActivity extinde Activitatea // Identificatorul mesajului utilizat pe Message.what () public public static final int KEY_MSG_IMAGE = 2; public final static public int KEY_MSG_PROGRESS = 3; public final static public int KEY_MSG_TOAST = 4;
Pe MessageHandler
punerea în aplicare, va trebui să ne extindem manipulant
și să pună în aplicare handleMessage (Mesaj)
, unde toate mesajele vor fi procesate. Observați că suntem prelungiți Message.what
pentru a identifica mesajul și de asemenea obținem date diferite Message.obj
. Să examinăm rapid cele mai importante Mesaj
înainte de scufundări în cod.
Message.what
: int
identificarea Mesaj
Message.arg1
: int
argument arbitrarMessage.arg2
: int
argument arbitrarMessage.obj
: Obiect
pentru a stoca diferite tipuri de dateclasa publică MessageActivity extinde Activitatea / ** * Handler responsabil pentru gestionarea comunicării * din @link WorkerThread. Se trimite Mesaje * înapoi la @ MessageActivity și manipulează * acele Mesaje * / clasa publică MessageHandler extinde Handler @Override public void handleMessage (Message msg) comutare (msg.what) // manipula cazul KEY_MSG_IMAGE: Bitmap bmp = (Bitmap) msg.obj; myImage.setImageBitmap (bmp); pauză; / / handle case de apel progressBar KEY_MSG_PROGRESS: if ((boolean) msg.obj) progressBar.setVisibility (View.VISIBLE); altceva progressBar.setVisibility (View.GONE); pauză; // manipulați toastul trimis cu un caz de întârziere a mesajului KEY_MSG_TOAST: String msgText = (String) msg.obj; Toast.makeText (getApplicationContext (), msgText, Toast.LENGTH_LONG) .show (); pauză; // Handler care permite comunicarea între // WorkerThread și Managerul de mesaje uiHandler protejat de activitate;
Acum să ne întoarcem la WorkerThread
clasă. Vom adăuga un cod pentru a descărca un bitmap specific și, de asemenea, pentru a descărca un cod aleatoriu. Pentru a îndeplini aceste sarcini, vom trimite Mesaj
obiecte din WorkerThread
la sine și trimite rezultatele înapoi la MessageActivity
utilizând exact aceeași logică aplicată anterior pentru RunnableActivity
.
Mai întâi trebuie să extindem manipulant
pentru a procesa mesajele descărcate.
clasa publică WorkerThread extinde HandlerThread // trimite și prelucrează mesajele descărcate pe workerThread private HandlerMsgImgDownloader handlerMsgImgDownloader; / ** * Cheile pentru identificarea cheilor din @link Message # what * din Mesajele trimise de @link # handlerMsgImgDownloader * / finale private int MSG_DOWNLOAD_IMG = 0; // msg că descărcați o singură intrare img privată int MSG_DOWNLOAD_RANDOM_IMG = 1; // msg care se descarcă la întâmplare img / ** * Handler responsabil pentru gestionarea descărcării imaginii * Trimite și manipulează mesajele care identifică folosind apoi * @ link-ul Message # what * @link #MSG_DOWNLOAD_IMG: o singură imagine * @link #MSG_DOWNLOAD_RANDOM_IMG: imagine aleatoare * / clasa privată HandlerMsgImgDownloader extinde Handler private HandlerMsgImgDownloader looper looper) super (looper); @Override public void handleMessage (Mesaj mesaj) showProgressMSG (true); comutare (msg.what) caz MSG_DOWNLOAD_IMG: // primește un singur url și îl descarcă url String = (String) msg.obj; downloadImageMSG (url); pauză; caz MSG_DOWNLOAD_RANDOM_IMG: // primește un String [] cu urlări multiple // descarcă o imagine aleatoriu String [] urls = (String []) msg.obj; Random aleator = nou Random (); String url = adresă URL [random.nextInt (urls.length)]; downloadImageMSG (url); showProgressMSG (false);
downloadImageMSG (URL-ul șirului)
metoda este, în esență, aceeași ca și downloadImage (url de coardă)
metodă. Singura diferență este că primul trimite bitmapul descărcat înapoi la UI prin trimiterea unui mesaj folosind responseHandler
.
clasa publică WorkerThread extinde HandlerThread / ** * Descărcați un bitmap utilizând urlul său și * îl afișați în interfață. * Singura diferență cu @link #downloadImage (String) * este faptul că trimite imaginea înapoi la UI * folosind un Mesaj * / void private downloadImageMSG (String urlStr) // Creează o conexiune HttpURLConnection connection = null; încercați URL url = URL nou (urlStr); conexiune = (HttpURLConnection) url.openConnection (); // a obține fluxul de la url InputStream in = BufferedInputStream nou (connection.getInputStream ()); bitmap bitmap final = BitmapFactory.decodeStream (in); dacă (bitmap! = null) // trimiteți bitmapul descărcat și un feedback către UI loadImageOnUIMSG (bitmap); captură (IOException e) e.printStackTrace (); în cele din urmă if (connection! = null) connection.disconnect ();
loadImageOnUIMSG (imagine bitmap)
este responsabil pentru trimiterea unui mesaj cu bitmap descărcat MessageActivity
.
/ ** * trimite o Bitmap către ui * trimite un Mesaj către @link #responseHandler * / void privat loadImageOnUIMSG (imagine bitmap finală) if (checkResponse ()) sendMsgToUI (responseHandler.get () getMessage MessageActivity.KEY_MSG_IMAGE, imagine)); / ** * Afișează / Ascunde bara de progres pe interfața utilizator. * Folosește linkul @link #responseHandler pentru a trimite * un mesaj pe UI * / spectacol privat privatProgressMSG (spectacol boolean) Log.d (TAG, "showProgressMSG ()"); dacă (checkResponse ()) sendMsgToUI (răspunsHandler.get (). getMessage (MessageActivity.KEY_MSG_PROGRESS, show));
Observați că în loc să creați o Mesaj
obiect de la zero, noi folosim Handler.obtainMessage (int ce, Object obj)
metodă de recuperare a Mesaj
din piscina globală, economisind niște resurse. De asemenea, este important să rețineți că sunăm obtainMessage ()
pe responseHandler
, obținerea unui Mesaj
asociat cu MessageActivity
„s maieză
. Există două modalități de a prelua a Mesaj
din piscina globală: Message.obtain ()
și Handler.obtainMessage ()
.
Singurul lucru pe care trebuie sa-l faci in sarcina de download a imaginii este sa furnizezi metodele de trimitere a Mesaj
la WorkerThread
pentru a începe procesul de descărcare. Observați că de data asta vom apela Message.obtain (Handler handler, int ce, Object obj)
pe handlerMsgImgDownloader
, asocierea mesajului cu WorkerThread
Câinele lui.
/ ** * trimite un Mesaj la Thread-ul curent * folosind @link # handlerMsgImgDownloader * pentru a descărca o singură imagine. * / void publice downloadWithMessage () Log.d (TAG, "downloadWithMessage ()"); showOperationOnUIMSG ("Trimiterea mesajului ..."); dacă (handlerMsgImgDownloader == null) handlerMsgImgDownloader = new HandlerMsgImgDownloader (getLooper ()); Mesaj mesaj = Message.obtain (handlerMsgImgDownloader, MSG_DOWNLOAD_IMG, imageBUrl); handlerMsgImgDownloader.sendMessage (mesaj); / ** * trimite un Mesaj către Thread-ul curent * folosind @link # handlerMsgImgDownloader * pentru a descărca o imagine aleatorie. * / void publice downloadRandomWithMessage () Log.d (TAG, "downloadRandomWithMessage ()"); showOperationOnUIMSG ("Trimiterea mesajului ..."); dacă (handlerMsgImgDownloader == null) handlerMsgImgDownloader = new HandlerMsgImgDownloader (getLooper ()); Mesaj mesaj = Message.obtain (handlerMsgImgDownloader, MSG_DOWNLOAD_RANDOM_IMG, imagesUrls); handlerMsgImgDownloader.sendMessage (mesaj);
O altă posibilitate interesantă este trimiterea Mesaj
obiecte care trebuie procesate ulterior cu comanda Message.sendMessageDelayed (Mesaj de mesaj, mult timpMillis)
.
/ ** * Afișează un toast după o întârziere. * * trimite un Mesaj cu timpul întârziat pe WorkerThread * și trimite un Mesaj nou către @link MessageActivity * cu un text după procesarea mesajului * / public void startMessageDelay () // delay întârziere mesaj = 5000; String msgText = "Salut de la WorkerThread!"; // Handler responsabil pentru trimiterea mesajului la WorkerThread // folosind Handler.Callback () pentru a evita necesitatea de a extinde Handler handler Handler class = new Handler (new Handler.Callback () @Override public boolean handleMessage (Message msg) responseHandler .get (). sendMessage (responseHandler.get ()) getMessage (MessageActivity.KEY_MSG_TOAST, msg.obj)); return true;); // trimiterea mesajului handler.sendMessageDelayed (handler.obtainMessage (0, msgText), întârziere);
Am creat a manipulant
în mod expres pentru trimiterea mesajului întârziat. În loc să se extindă manipulant
am luat calea de a instantiza a manipulant
folosind Handler.Callback
pentru care am implementat handleMessage (Mesaj mesaj)
pentru a procesa întârzierile Mesaj
.
Ați văzut deja un cod suficient pentru a înțelege cum să aplicați conceptele de bază HaMeR pentru a gestiona concurența pe Android. Există și alte caracteristici interesante ale proiectului final stocate pe GitHub și vă sfătuiesc cu tărie să verificați acest lucru.
În cele din urmă, am câteva considerații pe care trebuie să le țineți minte:
RetainedFragment
pentru a stoca firul și a popula firul de fond cu referința activității de fiecare dată când activitatea este distrusă. Uitați-vă la soluția din proiectul final pe GitHub.runnable
și Mesaj
obiecte procesate pe Handlere
nu rulați asincron. Vor rula sincron pe firul asociat cu dispozitivul de manipulare. Pentru ao face asincronă, va trebui să creați un alt fir, să trimiteți / să postați Mesaj
/runnable
obiect pe ea, și să primească rezultatele la momentul potrivit.După cum puteți vedea, cadrul HaMeR are o mulțime de posibilități diferite și este o soluție destul de deschisă, cu multe opțiuni pentru gestionarea concurenței pe Android. Aceste caracteristici pot fi avantaje AsyncTask
, în funcție de nevoile dvs. Explorați mai mult cadrul și citiți documentația și veți crea lucruri minunate cu acesta.
Ne vedem în curând!