Concorrenza pratica su Android con HaMeR

In Understanding Concurrency su Android Usando HaMeR, abbiamo parlato delle basi dell'HaMeR (handler, Messaggio, e Runnable) struttura. Abbiamo coperto le sue opzioni, nonché quando e come usarlo. 

Oggi creeremo una semplice applicazione per esplorare i concetti appresi. Con un approccio pratico, vedremo come applicare le diverse possibilità di HaMeR nella gestione della concorrenza su Android.

1. L'applicazione di esempio

Andiamo a lavorare e pubblichiamo alcuni Runnable e invia Messaggio oggetti su un'applicazione di esempio. Per mantenerlo il più semplice possibile, esploreremo solo le parti più interessanti. Tutti i file di risorse e le chiamate di attività standard verranno ignorati qui. Quindi ti consiglio vivamente di controllare il codice sorgente dell'applicazione di esempio con i suoi ampi commenti. 

L'app sarà composta da:

  • Due attività, una per Runnable un altro per Messaggio chiamate
  • Due HandlerThread oggetti:
    • WorkerThread per ricevere ed elaborare chiamate dall'interfaccia utente
    • controfilettatura ricevere Messaggio chiama dal WorkerThread
  • Alcune classi di utilità (per conservare oggetti durante le modifiche alla configurazione e per il layout)

2. Registrare e ricevere i runnables

Iniziamo a sperimentare con Handler.post (Runnable) metodo e le sue varianti, che aggiungono un eseguibile a a MessageQueue associato a un thread. Creeremo un'attività chiamata RunnableActivity, che comunica con un thread in background chiamato WorkerThread.

Il RunnableActivity crea un'istanza di un thread in background chiamato WorkerThread, passando a handler e a WorkerThread.Callback come parametri. L'attività può effettuare chiamate WorkerThread scaricare in modo asincrono una bitmap e mostrare un brindisi in un dato momento. I risultati delle attività eseguite dal thread di lavoro vengono passati a RunnableActivity da runnables pubblicato su handler ricevuto da WorkerThread.

2.1 Preparazione di un gestore per RunnableActivity

Sul RunnableActivity creeremo a handler essere passato a WorkerThread. Il uiHandler sarà associato al Looper dal thread dell'interfaccia utente, poiché viene chiamato da quel thread.

public class RunnableActivity estende Activity // Handler che consente la comunicazione tra // the WorkerThread e il gestore protetto dall'attività uiHandler; @Override protected void onCreate (Bundle savedInstanceState) super.onCreate (savedInstanceState); setContentView (R.layout.activity_main); // prepara l'UI Handler da inviare a WorkerThread uiHandler = new Handler (); 

2.2 Dichiarazione WorkerThread e la sua interfaccia di callback

Il WorkerThread è un thread in background in cui inizieremo diversi tipi di attività. Comunica con l'interfaccia utente usando il responseHandler e un'interfaccia di callback ricevuta durante la sua istanziazione. I riferimenti ricevuti dalle attività sono WeakReference <> digitare, poiché un'attività potrebbe essere distrutta e il riferimento perso.

La classe offre un'interfaccia che può essere implementata dall'interfaccia utente. Si estende anche HandlerThread, una classe di supporto costruita sopra Filo che contiene già un Looper, e a MessageQueue. Quindi ha il corretto impostareutilizzare il framework HaMeR.

public class WorkerThread estende l'interfaccia HandlerThread / ** * per facilitare le chiamate sull'interfaccia utente. * / callback dell'interfaccia pubblica void loadImage (immagine bitmap); void showToast (String msg);  // Questo handler sarà responsabile solo // per la pubblicazione di Runnables su questo gestore privato thread Thread postHandler; // Il gestore viene ricevuto da MessageActivity e RunnableActivity // responsabile della ricezione delle chiamate eseguibili che verranno elaborate // sull'interfaccia utente. Il callback aiuterà questo processo. WeakReference privato responseHandler; // Richiamata dall'interfaccia utente // è un WeakReference perché può essere invalidato // durante "modifiche alla configurazione" e altri eventi WeakReference privato richiama; private final String imageAUrl = "https://pixabay.com/static/uploads/photo/2016/08/05/18/28/mobile-phone-1572901_960_720.jpg"; / ** * Il costruttore riceve un handler e un callback dall'interfaccia utente * @param responseHandler responsabile della pubblicazione del Runnable nell'interfaccia utente * @ callback callam funziona insieme con responseHandler * che consente le chiamate direttamente sull'interfaccia utente * / public WorkerThread (Handler responseHandler, callback callback) super (TAG); this.responseHandler = new WeakReference <> (responseHandler); this.callback = new WeakReference <> (callback); 

2.3 Inizializzazione WorkerThread

Dobbiamo aggiungere un metodo a WorkerThread essere chiamato dalle attività che preparano il thread postHandler per uso. Il metodo deve essere chiamato solo dopo l'avvio del thread.

public class WorkerThread estende HandlerThread / ** * Prepara postHandler. * Deve essere chiamato dopo l'avvio del thread * / public void prepareHandler () postHandler = new Handler (getLooper ()); 

Sul RunnableActivity dobbiamo attuare WorkerThread.Callback e inizializza la discussione in modo che possa essere utilizzata.

public class RunnableActivity estende Activity implements WorkerThread.Callback // BackgroundThread responsabile del download dell'immagine WorkerThread worker protettoThread; / ** * Inizializza l'istanza @link WorkerThread * solo se non è ancora stata inizializzata. * / public void initWorkerThread () if (workerThread == null) workerThread = new WorkerThread (uiHandler, this); workerThread.start (); workerThread.prepareHandler ();  / ** * imposta l'immagine scaricata sul thread bg su imageView * / @Override public void loadImage (immagine Bitmap) myImage.setImageBitmap (image);  @Override public void showToast (final String msg) // da implementare

2.4 Utilizzo Handler.post () sul WorkerThread

Il WorkerThread.downloadWithRunnable () metodo scarica una bitmap e la invia a RunnableActivity per essere visualizzato in un'immagine Visualizza. Illustra due usi di base del Handler.post (Run Runable) comando:

  • Per consentire a Thread di inviare un oggetto Runnable a un MessageQueue associato a se stesso quando .inviare() viene chiamato su un gestore associato al Looper del thread.
  • Per consentire la comunicazione con altri thread, quando .inviare() viene chiamato su un handler associato ad Looper di altri thread.
  1. Il WorkerThread.downloadWithRunnable () metodo post a Runnable al WorkerThread'S MessageQueue usando il postHandler, un handler Associato a WorkThread'S Looper.
  2. Quando viene eseguito il runnable, scarica a Bitmap sul WorkerThread.
  3. Dopo aver scaricato la bitmap, il file responseHandler, un gestore associato al thread dell'interfaccia utente, viene utilizzato per pubblicare un eseguibile sul RunnableActivity contenente la bitmap.
  4. Il runnable viene elaborato e il WorkerThread.Callback.loadImage è usato per esporre l'immagine scaricata su un ImageView.
public class WorkerThread estende HandlerThread / ** * inserisci un Runnable per WorkerThread * Scarica una bitmap e invia l'immagine * all'interfaccia utente @link RunnableActivity * utilizzando il @link #responseHandler con * l'aiuto di @link #callback * / public void downloadWithRunnable () // post Runnable to WorkerThread postHandler.post (new Runnable () @Override public void run () try // sleeps per 2 secondi per emulare l'operazione di lunga durata TimeUnit.SECONDS .sleep (2); // Scarica immagine e invia all'interfaccia utente downloadImage (imageAUrl); catch (InterruptedException e) e.printStackTrace (););  / ** * Scarica una bitmap usando il suo url e * invia all'interfaccia utente l'immagine scaricata * / private void downloadImage (String urlStr) // Crea una connessione HttpURLConnection connection = null; prova URL url = new URL (urlStr); connection = (HttpURLConnection) url.openConnection (); // recupera il flusso dall'url InputStream in = new BufferedInputStream (connection.getInputStream ()); bitmap finale Bitmap = BitmapFactory.decodeStream (in); if (bitmap! = null) // invia la bitmap scaricata e un feedback all'interfaccia utente loadImageOnUI (bitmap);  else  catch (IOException e) e.printStackTrace ();  finally if (connection! = null) connection.disconnect ();  / ** * invia una Bitmap al file ui * postando un Runnable su @link #responseHandler * e utilizzando il @link Callback * / private void loadImageOnUI (immagine Bitmap finale) Log.d (TAG, "loadImageOnUI (" + immagine + ")"); if (checkResponse ()) responseHandler.get (). post (new Runnable () @Override public void run () callback.get (). loadImage (image););  // verifica se responseHandler è disponibile // se l'attività non sta passando per qualche evento di distruzione booleano privato checkResponse () return responseHandler.get ()! = null; 

2.5 Utilizzo Handler.postAtTime () e Activity.runOnUiThread ()

Il WorkerThread.toastAtTime ()pianifica un compito da eseguire in un determinato momento, esibendo a Crostini per l'utente. Il metodo illustra l'uso del Handler.postAtTime () e il Activity.runOnUiThread ().

  • Handler.postAtTime (Run runable, long uptimeMillis) pubblica un eseguibile in un dato momento.
  • Activity.runOnUiThread (Run eseguibile) utilizza il gestore dell'interfaccia utente predefinito per pubblicare un eseguibile nel thread principale.
public class WorkerThread estende HandlerThread / ** * mostra un Toast sull'interfaccia utente. * pianifica l'attività considerando l'ora corrente. * Può essere programmato in qualsiasi momento, * impieghiamo 5 secondi per facilitare il debug * / public void toastAtTime () Log.d (TAG, "toastAtTime (): current -" + Calendar.getInstance (). ToString ()); // secondi da aggiungere all'ora corrente int delaySeconds = 5; // test utilizzando una data reale Calendar scheduledDate = Calendar.getInstance (); // impostazione di una data futura considerando il ritardo in secondi definisce // stiamo usando questo approccio solo per facilitare il test. // potrebbe essere eseguito utilizzando una data definita dall'utente anche scheduledDate.set (scheduledDate.get (Calendar.YEAR), scheduledDate.get (Calendar.MONTH), scheduledDate.get (Calendar.DAY_OF_MONTH), scheduledDate.get (Calendar.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 at time time postHandler.postAtTime (new Runnable () @Override public void run () if (callback! = null) callback.get (). showToast ("Toast ha chiamato usando 'postAtTime ()'. ");, pianificato);  / ** * Calcola @link SystemClock # uptimeMillis () su * una data data del calendario. * / private long calculateUptimeMillis (Calendar calendar) long time = calendar.getTimeInMillis (); long currentTime = Calendar.getInstance (). getTimeInMillis (); long diff = time - currentTime; return SystemClock.uptimeMillis () + diff; 
public class RunnableActivity estende Activity implementa WorkerThread.Callback / ** * Callback da @link WorkerThread * Utilizza @link #runOnUiThread (Runnable) per illustrare * tale metodo * / @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 ();); 

3. Invio di messaggi con il MessageActivity & WorkerThread

Successivamente, esploriamo alcuni modi diversi di utilizzo MessageActivity  per inviare ed elaborare Messaggio oggetti. Il MessageActivity un'istanza WorkerThread, passando a handler come parametro Il WorkerThread ha alcuni metodi pubblici con attività che devono essere richiamate dall'attività per scaricare una bitmap, scaricare una bitmap casuale o esibire a Crostini dopo un po 'di tempo in ritardo. I risultati di tutte quelle operazioni vengono rimandati a MessageActivity utilizzando Messaggio oggetti inviati dal responseHandler.

3.1 Preparazione del gestore delle risposte da MessageActivity

Come nel RunnableActivity, nel MessageActivity dovremo istanziare e inizializzare a WorkerThread inviare un handler per ricevere dati dal thread in background. Tuttavia, questa volta non implementeremo WorkerThread.Callback; invece, riceveremo risposte dal WorkerThread esclusivamente da Messaggio oggetti.

Dal momento che la maggior parte del MessageActivity e RunnableActivity il codice è fondamentalmente lo stesso, ci concentreremo solo sul uiHandler preparazione, che sarà inviata a WorkerThread per ricevere messaggi da esso.

Per prima cosa, forniamone alcuni int chiavi da utilizzare come identificatori degli oggetti Messaggio.

public class MessageActivity estende Activity // Identificatore del messaggio utilizzato nel campo Message.what () public static final int KEY_MSG_IMAGE = 2; public static final int KEY_MSG_PROGRESS = 3; public static final int KEY_MSG_TOAST = 4; 

Sopra messageHandler implementazione, dovremo estendere handler e implementare il handleMessage (Messaggio) metodo, in cui verranno elaborati tutti i messaggi. Si noti che stiamo andando a prendere Message.what per identificare il messaggio, e stiamo anche ottenendo diversi tipi di dati da Message.obj. Rivediamo rapidamente il più importante Messaggio proprietà prima di immergersi nel codice.

  • Message.whatint identificando il Messaggio
  • Message.arg1int argomento arbitrario
  • Message.arg2int argomento arbitrario
  • Message.objOggetto per memorizzare diversi tipi di dati
public class MessageActivity estende Activity / ** * Handler responsabile della gestione della comunicazione * da @link WorkerThread. Invia Messaggi * al @link MessageActivity e gestisce * quei Messaggi * / public MessageHandler estende il Gestore @Override public void handleMessage (Message msg) switch (msg.what) // gestisce il caso immagine KEY_MSG_IMAGE:  Bitmap bmp = (Bitmap) msg.obj; myImage.setImageBitmap (BMP); rompere;  // gestisce progressBar chiama case KEY_MSG_PROGRESS: if ((booleano) msg.obj) progressBar.setVisibility (View.VISIBLE); else progressBar.setVisibility (View.GONE); rompere;  // gestisce il brindisi inviato con un caso di ritardo Messaggio KEY_MSG_TOAST: String msgText = (String) msg.obj; Toast.makeText (getApplicationContext (), msgText, Toast.LENGTH_LONG) .show (); rompere;  // Gestore che consente la comunicazione tra // il WorkerThread e l'Activity protected MessageHandler uiHandler;  

3.2 Invio di messaggi con WorkerThread

Ora torniamo al WorkerThread classe. Aggiungeremo del codice per scaricare una bitmap specifica e anche il codice per scaricarne uno casuale. Per eseguire tali compiti, invieremo Messaggio oggetti dal WorkerThread a se stesso e inviare i risultati a MessageActivity utilizzando esattamente la stessa logica applicata in precedenza per il RunnableActivity.

Per prima cosa dobbiamo estendere il handler per elaborare i messaggi scaricati.

public class WorkerThread estende HandlerThread // invia ed elabora i messaggi di download sul WorkerThread private HandlerMsgImgDownloader handlerMsgImgDownloader; / ** * Chiavi per identificare le chiavi di @link Messaggio # cosa * da Messaggi inviati da @link #handlerMsgImgDownloader * / private final int MSG_DOWNLOAD_IMG = 0; // msg che scarica un singolo img private final int MSG_DOWNLOAD_RANDOM_IMG = 1; // msg che scaricano il gestore img / ** * responsabile della gestione del download dell'immagine * Invia e gestisce i messaggi che identificano quindi usando * il @link Messaggio # cosa * @link #MSG_DOWNLOAD_IMG: immagine singola * @link #MSG_DOWNLOAD_RANDOM_IMG: immagine casuale * / classe privata HandlerMsgImgDownloader estende Handler private HandlerMsgImgDownloader (Looper looper) super (looper);  @Override public void handleMessage (Message msg) showProgressMSG (true); switch (msg.what) case MSG_DOWNLOAD_IMG: // riceve un singolo url e lo scarica String url = (String) msg.obj; downloadImageMSG (url); rompere;  caso MSG_DOWNLOAD_RANDOM_IMG: // riceve una stringa [] con più url // scarica un'immagine a caso String [] urls = (String []) msg.obj; Casuale casuale = nuovo Casuale (); String url = urls [random.nextInt (urls.length)]; downloadImageMSG (url);  showProgressMSG (false); 

Il downloadImageMSG (String URL) il metodo è fondamentalmente lo stesso di downloadImage (String URL) metodo. L'unica differenza è che il primo invia nuovamente la bitmap scaricata all'interfaccia utente inviando un messaggio utilizzando il comando responseHandler.

public class WorkerThread estende HandlerThread / ** * Scarica una bitmap usando il suo url e * la mostra all'interfaccia utente. * L'unica differenza con @link #downloadImage (String) * è che rimanda l'immagine all'interfaccia utente * utilizzando un messaggio * / private void downloadImageMSG (String urlStr) // Crea una connessione HttpURLConnection connection = null; prova URL url = new URL (urlStr); connection = (HttpURLConnection) url.openConnection (); // recupera il flusso dall'url InputStream in = new BufferedInputStream (connection.getInputStream ()); bitmap finale Bitmap = BitmapFactory.decodeStream (in); if (bitmap! = null) // invia la bitmap scaricata e un feedback all'interfaccia utente loadImageOnUIMSG (bitmap);  catch (IOException e) e.printStackTrace ();  finally if (connection! = null) connection.disconnect (); 

Il loadImageOnUIMSG (immagine bitmap) è responsabile dell'invio di un messaggio con la bitmap scaricata su MessageActivity

 / ** * invia un Bitmap all'interfaccia ui * inviando un messaggio a @link #responseHandler * / private void loadImageOnUIMSG (immagine bitmap finale) if (checkResponse ()) sendMsgToUI (responseHandler.get (). obtainMessage ( MessageActivity.KEY_MSG_IMAGE, image));  / ** * Mostra / nascondi progressBar sull'interfaccia utente. * Utilizza @link #responseHandler per * inviare un messaggio sull'interfaccia utente * / private void showProgressMSG (booleano show) Log.d (TAG, "showProgressMSG ()"); if (checkResponse ()) sendMsgToUI (responseHandler.get (). obtainMessage (MessageActivity.KEY_MSG_PROGRESS, show)); 

Si noti che invece di creare un Messaggio oggetto da zero, stiamo usando il Handler.obtainMessage (int what, Object obj) metodo per recuperare a Messaggio dal pool globale, risparmiando alcune risorse. È anche importante notare che stiamo chiamando il obtainMessage () sul responseHandler, ottenendo a Messaggio Associato a MessageActivity'S Looper. Ci sono due modi per recuperare a Messaggio dal pool globale: Message.obtain () e Handler.obtainMessage ().

L'unica cosa che rimane da fare nell'attività di download delle immagini è fornire i metodi per inviare a Messaggio a WorkerThread per iniziare il processo di download. Notare che questa volta chiameremo Message.obtain (Handler handler, int what, Object obj) sopra handlerMsgImgDownloader, associando il messaggio con WorkerThreadLooper.

 / ** * invia un messaggio al thread corrente * utilizzando @link #handlerMsgImgDownloader * per scaricare una singola immagine. * / public void downloadWithMessage () Log.d (TAG, "downloadWithMessage ()"); showOperationOnUIMSG ("Invio messaggio ..."); if (handlerMsgImgDownloader == null) handlerMsgImgDownloader = new HandlerMsgImgDownloader (getLooper ()); Messaggio messaggio = Message.obtain (handlerMsgImgDownloader, MSG_DOWNLOAD_IMG, imageBUrl); handlerMsgImgDownloader.sendMessage (messaggio);  / ** * invia un messaggio al thread corrente * utilizzando @link #handlerMsgImgDownloader * per scaricare un'immagine casuale. * / public void downloadRandomWithMessage () Log.d (TAG, "downloadRandomWithMessage ()"); showOperationOnUIMSG ("Invio messaggio ..."); if (handlerMsgImgDownloader == null) handlerMsgImgDownloader = new HandlerMsgImgDownloader (getLooper ()); Messaggio messaggio = Message.obtain (handlerMsgImgDownloader, MSG_DOWNLOAD_RANDOM_IMG, imagesUrls); handlerMsgImgDownloader.sendMessage (messaggio); 

Un'altra possibilità interessante è l'invio Messaggio oggetti da elaborare in un secondo momento con il comando Message.sendMessageDelayed (messaggio msg, long timeMillis).

/ ** * Mostra un toast dopo un ritardo. * * invia un messaggio con un tempo di ritardo sul WorkerThread * e invia un nuovo messaggio a @link MessageActivity * con un testo dopo che il messaggio è stato elaborato * / public void startMessageDelay () // message delay long delay = 5000; String msgText = "Ciao da WorkerThread!"; // Gestore responsabile per l'invio di Message to WorkerThread // utilizzando Handler.Callback () per evitare la necessità di estendere il gestore Handler della classe handler = new Handler (new Handler.Callback () @ Override public boolean handleMessage (Message msg) responseHandler .get (). sendMessage (responseHandler.get (). obtainMessage (MessageActivity.KEY_MSG_TOAST, msg.obj)); restituisce true;); // invio messaggio handler.sendMessageDelayed (handler.obtainMessage (0, msgText), ritardo); 

Abbiamo creato a handler espressamente per l'invio del messaggio in ritardo. Invece di estendere il handler classe, abbiamo preso il percorso di istanziare a handler usando il Handler.Callback interfaccia, per la quale abbiamo implementato il handleMessage (messaggio msg) metodo per elaborare il ritardo Messaggio.

4. Conclusione

Hai già visto abbastanza codice per capire come applicare i concetti di base HaMeR per gestire la concorrenza su Android. Ci sono altre interessanti funzionalità del progetto finale memorizzate su GitHub e ti consiglio vivamente di verificarlo. 

Infine, ho alcune ultime considerazioni da tenere a mente:

  • Non dimenticare di prendere in considerazione il ciclo di vita delle attività di Android quando si lavora con HaMeR e Thread in generale. In caso contrario, l'app potrebbe non riuscire quando il thread tenta di accedere ad attività che sono state distrutte a causa di modifiche alla configurazione o per altri motivi. Una soluzione comune è usare a RetainedFragment per memorizzare il thread e popolare il thread in background con il riferimento dell'attività ogni volta che l'attività viene distrutta. Dai un'occhiata alla soluzione nel progetto finale su GitHub.
  • Attività eseguite a causa di RunnableMessaggio oggetti elaborati su handlers non eseguire in modo asincrono. Verranno eseguiti in modo sincrono sul thread associato al gestore. Per renderlo asincrono, dovrai creare un altro thread, inviare / pubblicare il file Messaggio/Runnable oggetto su di esso e ricevere i risultati al momento opportuno.

Come potete vedere, il framework HaMeR ha molte possibilità diverse, ed è una soluzione abbastanza aperta con molte opzioni per gestire la concorrenza su Android. Queste caratteristiche possono essere vantaggi sopra AsyncTask, a seconda delle esigenze. Esplora altre parti del framework e leggi la documentazione e creerai grandi cose con esso.

A presto!