Concurs practic pe Android cu HaMeR

Î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.

1. Aplicația de probă

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:

  • Două activități, una pentru runnable altul pentru Mesaj apeluri
  • Două HandlerThread obiecte:
    • WorkerThread pentru a primi și a procesa apelurile de la interfața utilizator
    • CounterThread a primi Mesaj apeluri de la WorkerThread
  • Unele clase de utilitare (pentru a păstra obiectele în timpul modificărilor de configurație și pentru aspect)

2. Detașarea și primirea de runne

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.

2.1 Pregătirea unui Handler pentru RunnableActivity

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 (); 

2.2 Declararea 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 WeakReference responseHandler; // 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); 

2.3 Inițializarea 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

2.4 Utilizarea 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:

  • Pentru a permite unui Thread să posteze un obiect care poate fi rulat într-o MessageQueue asociată cu ea însăși când .post() este chemat pe un Handler asociat cu Looper's Thread.
  • Pentru a permite comunicarea cu alte Threads, când .post() este chemat pe un Handler asociat cu Looper lui Other Thread.
  1.  WorkerThread.downloadWithRunnable () postările de metodă a runnable la WorkerThread„s MessageQueue folosind postHandler, A manipulant asociat cu WorkThread„s maieză.
  2. Când se procesează runnable, se descarcă a Bitmap pe WorkerThread.
  3. După ce bitmap-ul este descărcat, responseHandler, un handler asociat cu firul UI, este folosit pentru a posta un runnable pe RunnableActivity care conține bitmap.
  4. Rularea este procesată, iar 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; 

2.5 Utilizarea 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 ();); 

3. Trimiterea de mesaje cu 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.

3.1 Pregătirea Handlerului de răspuns de la 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.whatint identificarea Mesaj
  • Message.arg1int argument arbitrar
  • Message.arg2int argument arbitrar
  • Message.objObiect pentru a stoca diferite tipuri de date
clasa 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;  

3.2 Trimiterea mesajelor cu WorkerThread

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 WorkerThreadCâ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.

4. Concluzie

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:

  • Nu uitați să luați în considerare ciclul de viață al activității Android atunci cand lucrezi cu HaMeR si Threads in general. Altfel, este posibil ca aplicația dvs. să nu reușească atunci când firul încearcă să acceseze activități care au fost distruse din cauza modificărilor de configurare sau din alte motive. O soluție comună este utilizarea unui a 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.
  • Sarcini care se execută din cauza 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!

Cod