En Entendiendo la concurrencia en Android usando HaMeR, hablamos de los conceptos básicos de HaMeR (Entrenador de animales
, Mensaje
, y Ejecutable
) marco de referencia. Cubrimos sus opciones, así como cuándo y cómo usarlo..
Hoy, crearemos una aplicación sencilla para explorar los conceptos aprendidos. Con un enfoque práctico, veremos cómo aplicar las diferentes posibilidades de HaMeR en la gestión de la concurrencia en Android.
Pongámonos a trabajar y publiquemos algunos. Ejecutable
y enviar Mensaje
Objetos en una aplicación de ejemplo. Para que sea lo más simple posible, exploraremos solo las partes más interesantes. Aquí se ignorarán todos los archivos de recursos y las llamadas de actividad estándar. Por eso le recomiendo encarecidamente que revise el código fuente de la aplicación de muestra con sus extensos comentarios..
La aplicación constará de:
Ejecutable
otro para Mensaje
llamadasHandlerThread
objetos:Trabajador
para recibir y procesar llamadas desde la interfaz de usuarioContrahilo
para recibir Mensaje
llamadas desde el Trabajador
Comencemos experimentando con el Handler.post (Ejecutable)
método y sus variaciones, que agregan un ejecutable a un Cola de mensajes
asociado a un hilo. Vamos a crear una actividad llamada Ejecutable
, que se comunica con un hilo de fondo llamado Trabajador
.
los Ejecutable
crea una instancia de un hilo de fondo llamado Trabajador
, pasando un Entrenador de animales
y un WorkerThread.Callback
como parametros La actividad puede hacer llamadas en Trabajador
para descargar de forma asíncrona un mapa de bits y mostrar un brindis en un momento determinado. Los resultados de las tareas realizadas por el hilo de trabajo se pasan a Ejecutable
por runnables publicado en el Entrenador de animales
Recibido por Trabajador
.
Sobre el Ejecutable
vamos a crear un Entrenador de animales
para ser pasado a Trabajador
. los uiHandler
será asociado con el Looper
del subproceso de la interfaz de usuario, ya que se llama desde ese subproceso.
la clase pública RunnableActivity extiende la Actividad // Handler que permite la comunicación entre // el WorkerThread y la Activity protected Handler uiHandler; @Override protected void onCreate (Bundle savedInstanceState) super.onCreate (savedInstanceState); setContentView (R.layout.activity_main); // preparar el controlador de UI para enviarlo a WorkerThread uiHandler = new Handler ();
Trabajador
y su interfaz de devolución de llamadalos Trabajador
Es un hilo de fondo donde comenzaremos diferentes tipos de tareas. Se comunica con la interfaz de usuario utilizando el respuestaHandler
y una interfaz de devolución de llamada recibida durante su instanciación. Las referencias recibidas de las actividades son: Referencia débil <>
Tipo, ya que una actividad podría ser destruida y la referencia perdida..
La clase ofrece una interfaz que puede ser implementada por la interfaz de usuario. También se extiende HandlerThread
, una clase de ayuda construida sobre Hilo
que ya contiene una Looper
, y un Cola de mensajes
. De ahí que tenga el correcto. prepararusar el framework HaMeR.
la clase pública WorkerThread amplía la interfaz HandlerThread / ** * para facilitar las llamadas en la interfaz de usuario. * / public interface Callback void loadImage (imagen de mapa de bits); void showToast (String msg); // Este controlador será el único responsable // de publicar Runnables en este Thread private Handler postHandler; // El controlador se recibe de MessageActivity y RunnableActivity // responsable de recibir las llamadas de Runnable que se procesarán // en la interfaz de usuario. La devolución de llamada ayudará a este proceso. debilidad privadarespuestaHandler; // Devolución de llamada desde la interfaz de usuario // es una WeakReference porque se puede invalidar // durante los "cambios de configuración" y otros eventos WeakReference privados llamar de vuelta; Cadena final privada imageAUrl = "https://pixabay.com/static/uploads/photo/2016/08/05/18/28/mobile-phone-1572901_960_720.jpg"; / ** * El constructor recibe un controlador y una devolución de llamada de la interfaz de usuario * @param responseHandler a cargo de la publicación del Runnable en la interfaz de usuario * @param devolución de llamada funciona junto con responseHandler * permitiendo llamadas directamente en la interfaz de usuario * / public WorkerThread (Handler responseHandler, devolución de llamada de devolución de llamada) super (TAG); this.responseHandler = new WeakReference <> (responseHandler); this.callback = new WeakReference <> (callback);
Trabajador
Necesitamos agregar un método para Trabajador
Ser llamado por las actividades que preparan el hilo. postHandler
para usar. El método debe llamarse solo después de que se inicie el hilo.
public class WorkerThread extiende HandlerThread / ** * Prepare el postHandler. * Se debe llamar después de que el hilo haya comenzado * / public void prepareHandler () postHandler = new Handler (getLooper ());
Sobre el Ejecutable
debemos implementar WorkerThread.Callback
e inicializa el hilo para que pueda ser utilizado.
la clase pública RunnableActivity extiende la actividad Implementa WorkerThread.Callback // BackgroundThread, responsable de descargar la imagen protegida WorkerThread workerThread; / ** * Inicialice la instancia @link WorkerThread * solo si aún no se ha inicializado. * / public void initWorkerThread () if (workerThread == null) workerThread = new WorkerThread (uiHandler, this); workerThread.start (); workerThread.prepareHandler (); / ** * establece la imagen descargada en el subproceso bg en imageView * / @Override public void loadImage (imagen de mapa de bits) myImage.setImageBitmap (image); @Override public void showToast (msg. De cadena final) // a implementar
Handler.post ()
en el hilo de trabajolos WorkerThread.downloadWithRunnable ()
Método descarga un mapa de bits y lo envía a Ejecutable
para ser mostrado en una vista de imagen. Ilustra dos usos básicos de la Handler.post (Runnable run)
mando:
.enviar()
se llama en un Handler asociado con el Looper de Thread..enviar()
es llamado en un Handler asociado con otro Looper de Hilo. WorkerThread.downloadWithRunnable ()
método publica un Ejecutable
al Trabajador
es Cola de mensajes
utilizando la postHandler
, una Entrenador de animales
asociado con Trabajo
es Looper
.Mapa de bits
sobre el Trabajador
.respuestaHandler
, un controlador asociado con el subproceso de la interfaz de usuario, se utiliza para publicar un runnable en el Ejecutable
que contiene el mapa de bits.WorkerThread.Callback.loadImage
se utiliza para exhibir la imagen descargada en una ImageView
.public class WorkerThread extiende HandlerThread / ** * publica un Runnable to WorkerThread * Descarga un mapa de bits y envía la imagen * a la interfaz de usuario @link RunnableActivity * usando el @link #responseHandler con * ayuda desde el @link #callback * / public void downloadWithRunnable () // post Runnable to WorkerThread postHandler.post (new Runnable () @Override public void run () try // sleeps durante 2 segundos para emular la operación de larga duración TimeUnit.SECONDS .sleep (2); // Descargar imagen y enviarla a UI downloadImage (imageAUrl); catch (InterruptedException e) e.printStackTrace ();); / ** * Descargue un mapa de bits con su url y * envíe a la UI la imagen descargada * / private void downloadImage (String urlStr) // Cree una conexión HttpURLConnection connection = null; intente URL url = nueva URL (urlStr); connection = (HttpURLConnection) url.openConnection (); // obtener el flujo de la url InputStream en = new BufferedInputStream (connection.getInputStream ()); Bitmap final bitmap = BitmapFactory.decodeStream (in); if (bitmap! = null) // envía el bitmap descargado y un comentario a la interfaz de usuario loadImageOnUI (bitmap); else catch (IOException e) e.printStackTrace (); finalmente if (connection! = null) connection.disconnect (); / ** * envía un Bitmap a la interfaz de usuario * publicando un Runnable al @link #responseHandler * y usando @link Callback * / private void loadImageOnUI (imagen de Bitmap final) Log.d (TAG, "loadImageOnUI (" + image + ")"); if (checkResponse ()) responseHandler.get (). post (new Runnable () @Override public void run () callback.get (). loadImage (image);); // verifique si responseHandler está disponible // si no, la Actividad está pasando por algún evento de destrucción privado booleano checkResponse () return responseHandler.get ()! = null;
Handler.postAtTime ()
y Activity.runOnUiThread ()
los WorkerThread.toastAtTime ()
programa una tarea para ser ejecutada en un cierto tiempo, exhibiendo un tostada
al usuario. El método ilustra el uso de la Handler.postAtTime ()
y el Activity.runOnUiThread ()
.
Handler.postAtTime (Runnable run, long uptimeMillis)
publica un ejecutable en un momento dado.Activity.runOnUiThread (Runnable run)
utiliza el controlador de interfaz de usuario predeterminado para publicar un runnable en el hilo principal.public class WorkerThread extiende HandlerThread / ** * muestra un Toast en la interfaz de usuario. * programa la tarea teniendo en cuenta la hora actual. * Se puede programar en cualquier momento, estamos * usando 5 segundos para facilitar la depuración * / public void toastAtTime () Log.d (TAG, "toastAtTime (): current -" + Calendar.getInstance (). ToString ()); // segundos para agregar en la hora actual int delaySeconds = 5; // prueba usando una fecha real Calendar scheduleDate = Calendar.getInstance (); // establecer una fecha futura considerando la demora en segundos definida // estamos usando este enfoque solo para facilitar las pruebas. // se podría hacer usando una fecha definida por el usuario también scheduleDate.set (scheduleDate.get (Calendar.YEAR), scheduleDate.get (Calendar.MONTH), scheduleDate.get (Calendar.DAY_OF_MONTH), scheduleDate.get (Calendar.HOUR_OF_DAY ), scheduleDate.get (Calendar.MINUTE), scheduleDate.get (Calendar.SECOND) + delaySeconds); Log.d (TAG, "toastAtTime (): programación en -" + scheduleDate.toString ()); long schedule = calculaUptimeMillis (scheduleDate); // publicar Runnable en un momento específico postHandler.postAtTime (new Runnable () @Override public void run () if (callback! = null) callback.get (). showToast ("Toast se llama utilizando 'postAtTime ()'. ");, programado); / ** * Calcula el @link SystemClock # uptimeMillis () a * una fecha del calendario dada. * / private long calculaUptimeMillis (calendario) long time = calendar.getTimeInMillis (); long currentTime = Calendar.getInstance (). getTimeInMillis (); long diff = time - currentTime; devuelve SystemClock.uptimeMillis () + diff;
public class RunnableActivity extiende la actividad Implementa WorkerThread.Callback / ** * Callback from @link WorkerThread * Utiliza @link #runOnUiThread (Runnable) para ilustrar * dicho método * / @Override public void showToast (String final String) ( Log.d (TAG, "showToast (" + msg + ")"); runOnUiThread (new Runnable () @Override public void run () Toast.makeText (getApplicationContext (), msg, Toast.LENGTH_LONG) .show (););
MensajeActividad
Y Trabajador
A continuación, vamos a explorar algunas formas diferentes de usar MensajeActividad
enviar y procesar Mensaje
objetos. los MensajeActividad
instancia Trabajador
, pasando un Entrenador de animales
como parámetro los Trabajador
tiene algunos métodos públicos con tareas a las que la actividad puede llamar para descargar un mapa de bits, descargar un mapa de bits aleatorio o exhibir un tostada
después de algún tiempo retrasado. Los resultados de todas esas operaciones se envían de vuelta a MensajeActividad
utilizando Mensaje
objetos enviados por el respuestaHandler
.
MensajeActividad
Como en el Ejecutable
, en el MensajeActividad
tendremos que instanciar e inicializar una Trabajador
enviando un Entrenador de animales
para recibir datos del hilo de fondo. Sin embargo, esta vez no implementaremos WorkerThread.Callback
; en cambio, recibiremos respuestas de la Trabajador
exclusivamente por Mensaje
objetos.
Como la mayoría de los MensajeActividad
y Ejecutable
El código es básicamente el mismo, nos concentraremos solo en el uiHandler
preparación, que será enviado a Trabajador
para recibir mensajes de ella.
En primer lugar, vamos a proporcionar algunos En t
Claves para ser utilizadas como identificadores de los objetos de mensaje..
public class MessageActivity extiende la Actividad // Identificador de mensaje utilizado en el campo Message.what () public static final int KEY_MSG_IMAGE = 2; public static final int KEY_MSG_PROGRESS = 3; final estático público int KEY_MSG_TOAST = 4;
En MessageHandler
Implementación, tendremos que ampliar Entrenador de animales
e implementar el handleMessage (Mensaje)
Método, donde se procesarán todos los mensajes. Fíjate que estamos recogiendo Mensaje
para identificar el mensaje, y también estamos obteniendo diferentes tipos de datos de Mensaje.obj
. Repasemos rápidamente lo más importante. Mensaje
propiedades antes de sumergirse en el código.
Mensaje
: En t
identificando el Mensaje
Mensaje.arg1
: En t
argumento arbitrarioMensaje.arg2
: En t
argumento arbitrarioMensaje.obj
: Objeto
para almacenar diferentes tipos de datospublic class MessageActivity extiende la actividad / ** * El manejador es responsable de gestionar la comunicación * desde @link WorkerThread. Envía Mensajes * de vuelta a @link MessageActivity y maneja * esos Mensajes * / clase pública MessageHandler extiende el Controlador @Override public void handleMessage (Message msg) switch (msg.what) // manejar caso de imagen KEY_MSG_IMAGE: Bitmap bmp = (Bitmap) msg.obj; myImage.setImageBitmap (bmp); descanso; // manejar el caso de las llamadas a progressBar KEY_MSG_PROGRESS: if ((boolean) msg.obj) progressBar.setVisibility (View.VISIBLE); else progressBar.setVisibility (View.GONE); descanso; // maneja el brindis enviado con un caso de retardo de mensaje KEY_MSG_TOAST: String msgText = (String) msg.obj; Toast.makeText (getApplicationContext (), msgText, Toast.LENGTH_LONG) .show (); descanso; // Controlador que permite la comunicación entre // el WorkerThread y el ActivityHandler MessageHandler protegido;
Ahora volvamos a la Trabajador
clase. Agregaremos algo de código para descargar un mapa de bits específico y también código para descargar uno aleatorio. Para llevar a cabo esas tareas, le enviaremos Mensaje
objetos de la Trabajador
a sí mismo y enviar los resultados de nuevo a MensajeActividad
utilizando exactamente la misma lógica aplicada anteriormente para el Ejecutable
.
Primero necesitamos extender el Entrenador de animales
para procesar los mensajes descargados.
public class WorkerThread extiende HandlerThread // envía y procesa los mensajes de descarga en el WorkerThread private HandlerMsgImgDownloader handlerMsgImgDownloader; / ** * Teclas para identificar las claves de @link Message # what * de los mensajes enviados por @link #handlerMsgImgDownloader * / private final int MSG_DOWNLOAD_IMG = 0; // mensaje que descarga una única img final privada int MSG_DOWNLOAD_RANDOM_IMG = 1; // msg que descarga aleatoriamente img / ** * Controlador responsable de administrar la descarga de la imagen * Envía y administra los mensajes identificando y luego usando * el @link Message # what * @link #MSG_DOWNLOAD_IMG: imagen única * @link #MSG_DOWNLOAD_RANDOM_IMG: imagen aleatoria * / clase privada HandlerMsgImgDownloader extiende Handler private HandlerMsgImgDownloader (Looper looper) super (looper); @ Anular pública void handleMessage (Mensaje msg) showProgressMSG (true); switch (msg.what) case MSG_DOWNLOAD_IMG: // recibe una única url y la descarga String url = (String) msg.obj; descargarImageMSG (url); descanso; caso MSG_DOWNLOAD_RANDOM_IMG: // recibe un String [] con múltiples urls // descarga una imagen al azar String [] urls = (String []) msg.obj; Aleatorio aleatorio = nuevo Random (); Cadena url = urls [random.nextInt (urls.length)]; descargarImageMSG (url); showProgressMSG (falso);
los descargarImageMSG (url de cadena)
El método es básicamente el mismo que el descargarImage (String url)
método. La única diferencia es que el primero envía el mapa de bits descargado de nuevo a la interfaz de usuario enviando un mensaje usando respuestaHandler
.
public class WorkerThread extiende HandlerThread / ** * Descargue un mapa de bits usando su url y * muéstrelo a la interfaz de usuario. * La única diferencia con @link #downloadImage (String) * es que envía la imagen a la UI * mediante un Mensaje * / private void downloadImageMSG (String urlStr) // Crear una conexión HttpURLConnection connection = null; intente URL url = nueva URL (urlStr); connection = (HttpURLConnection) url.openConnection (); // obtener el flujo de la url InputStream en = new BufferedInputStream (connection.getInputStream ()); Bitmap final bitmap = BitmapFactory.decodeStream (in); if (bitmap! = null) // envía el bitmap descargado y un feedback a la interfaz de usuario loadImageOnUIMSG (bitmap); catch (IOException e) e.printStackTrace (); finalmente if (connection! = null) connection.disconnect ();
los loadImageOnUIMSG (imagen de mapa de bits)
es responsable de enviar un mensaje con el mapa de bits descargado a MensajeActividad
.
/ ** * envía un mapa de bits a la interfaz de usuario * enviando un mensaje al @link #responseHandler * / private void loadImageOnUIMSG (imagen de mapa de bits final) if (checkResponse ()) sendMsgToUI (responseHandler.get (). Obtenir mensaje) MessageActivity.KEY_MSG_IMAGE, imagen)); / ** * Mostrar / Ocultar barra de progreso en la interfaz de usuario. * Utiliza el @link #responseHandler para * enviar un mensaje en la UI * / private void showProgressMSG (show booleano) Log.d (TAG, "showProgressMSG ()"); if (checkResponse ()) sendMsgToUI (responseHandler.get (). obtieneMessage (MessageActivity.KEY_MSG_PROGRESS, show));
Tenga en cuenta que en lugar de crear un Mensaje
Objeto desde cero, estamos usando el Handler.obtainMessage (int what, Object obj)
método para recuperar un Mensaje
desde el pool global, ahorrando algunos recursos. También es importante tener en cuenta que estamos llamando a la obtener mensaje ()
sobre el respuestaHandler
, obteniendo un Mensaje
asociado con MensajeActividad
es Looper
. Hay dos formas de recuperar un Mensaje
del grupo global: Mensaje.obtain ()
y Handler.obtainMessage ()
.
Lo único que queda por hacer en la tarea de descarga de imágenes es proporcionar los métodos para enviar un Mensaje
a Trabajador
para iniciar el proceso de descarga. Fíjate que esta vez llamaremos Message.obtain (Handler handler, int what, Object obj)
en handlerMsgImgDownloader
, asociando el mensaje con Trabajador
looper.
/ ** * envía un Mensaje al hilo actual * utilizando el @link #handlerMsgImgDownloader * para descargar una sola imagen. * / public void downloadWithMessage () Log.d (TAG, "downloadWithMessage ()"); showOperationOnUIMSG ("Enviando mensaje ..."); if (handlerMsgImgDownloader == null) handlerMsgImgDownloader = new HandlerMsgImgDownloader (getLooper ()); Mensaje message = Message.obtain (handlerMsgImgDownloader, MSG_DOWNLOAD_IMG, imageBUrl); handlerMsgImgDownloader.sendMessage (mensaje); / ** * envía un mensaje al hilo actual * utilizando el @link #handlerMsgImgDownloader * para descargar una imagen al azar. * / public void downloadRandomWithMessage () Log.d (TAG, "downloadRandomWithMessage ()"); showOperationOnUIMSG ("Enviando mensaje ..."); if (handlerMsgImgDownloader == null) handlerMsgImgDownloader = new HandlerMsgImgDownloader (getLooper ()); Mensaje message = Message.obtain (handlerMsgImgDownloader, MSG_DOWNLOAD_RANDOM_IMG, imagesUrls); handlerMsgImgDownloader.sendMessage (mensaje);
Otra posibilidad interesante es el envío. Mensaje
Objetos a procesar posteriormente con el comando. Message.sendMessageDelayed (Mensaje msg, long timeMillis)
.
/ ** * Mostrar un brindis después de un tiempo de retraso. * * envía un mensaje con tiempo de retraso en WorkerThread * y envía un nuevo mensaje a @link MessageActivity * con un texto después de que se procese el mensaje * / public void startMessageDelay () // message delay long delay = 5000; String msgText = "¡Hola desde WorkerThread!"; // Manejador responsable de enviar el mensaje a WorkerThread // usando Handler.Callback () para evitar la necesidad de extender la clase Handler Handler handler = new Handler (new Handler.Callback () @Override public boolean handleMessage (Mensaje del mensaje) responseHandler .get (). sendMessage (responseHandler.get (). obtieneMessage (MessageActivity.KEY_MSG_TOAST, msg.obj)); return true;); // enviando el mensaje handler.sendMessageDelayed (handler.obtainMessage (0, msgText), retraso);
Creamos un Entrenador de animales
Expresamente por enviar el mensaje retrasado. En lugar de extender el Entrenador de animales
clase, tomamos la ruta de instanciar una Entrenador de animales
usando el Handler.Callback
interfaz, para lo cual implementamos el handleMessage (Mensaje msg)
Método para procesar el retraso Mensaje
.
Ya ha visto suficiente código para entender cómo aplicar los conceptos básicos del marco de HaMeR para administrar la concurrencia en Android. Hay otras características interesantes del proyecto final almacenado en GitHub, y le recomiendo que lo revise..
Finalmente, tengo algunas consideraciones finales que debes tener en cuenta:
RetenidoFragmento
para almacenar el hilo y rellenar el hilo de fondo con la referencia de la actividad cada vez que se destruye la actividad. Eche un vistazo a la solución en el proyecto final en GitHub.Ejecutable
y Mensaje
objetos procesados en Manipuladores
no correr asincronicamente. Se ejecutarán de forma síncrona en el subproceso asociado con el controlador. Para hacerlo asíncrono, deberá crear otro hilo, enviar / publicar el Mensaje
/Ejecutable
objetar en él, y recibir los resultados en el momento adecuado.Como puede ver, el marco de trabajo de HaMeR tiene muchas posibilidades diferentes y es una solución bastante abierta con muchas opciones para administrar la concurrencia en Android. Estas características pueden ser ventajas sobre AsyncTask
, Dependiendo de tus necesidades. Explore más del marco y lea la documentación, y creará grandes cosas con él.
Te veo pronto!