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.
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:
Runnable
un altro per Messaggio
chiamateHandlerThread
oggetti:WorkerThread
per ricevere ed elaborare chiamate dall'interfaccia utentecontrofilettatura
ricevere Messaggio
chiama dal WorkerThread
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
.
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 ();
WorkerThread
e la sua interfaccia di callbackIl 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 privatoresponseHandler; // 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);
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
Handler.post ()
sul WorkerThreadIl 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:
.inviare()
viene chiamato su un gestore associato al Looper del thread..inviare()
viene chiamato su un handler associato ad Looper di altri thread. WorkerThread.downloadWithRunnable ()
metodo post a Runnable
al WorkerThread
'S MessageQueue
usando il postHandler
, un handler
Associato a WorkThread
'S Looper
.Bitmap
sul WorkerThread
.responseHandler
, un gestore associato al thread dell'interfaccia utente, viene utilizzato per pubblicare un eseguibile sul RunnableActivity
contenente la bitmap.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;
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 (););
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
.
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.what
: int
identificando il Messaggio
Message.arg1
: int
argomento arbitrarioMessage.arg2
: int
argomento arbitrarioMessage.obj
: Oggetto
per memorizzare diversi tipi di datipublic 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;
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 WorkerThread
Looper.
/ ** * 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
.
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:
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.Runnable
e Messaggio
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!