Advertisement
  1. Code
  2. Android SDK
Code

Práctica de concurrencia en Android con HaMeR

by
Difficulty:IntermediateLength:LongLanguages:

Spanish (Español) translation by Pablo Fidalgo (you can also view the original English article)

En Understanding Concurrency on Android Using HaMeR, hablamos de la base del framework HaMeR (Handler, Message y Runnable). Se habló de sus posibilidades así como de cuando y en qué modo hacer uso de él.

Hoy, crearemos una aplicación sencilla para explorar los conceptos aprendidos. Con un enfoque práctico, te mostraremos cómo emplear las diferentes posibilidades de HaMeR en la gestión de la concurrencia en Android.

1. La aplicación de ejemplo

Vamos a trabajar y postear algún Runnable y a enviar objetos Message en una aplicación de ejemplo. Para hacerlo lo más sencillo posible vamos a explorar solo las partes mas interesantes. Todos los ficheros de recursos y las llamadas a activity estándares son aquí ignoradas. De modo que te recomiendo que consultes el código fuente de la aplicación de ejemplo con sus comentarios.

HaMeR sample application

La aplicación consta de :

  • Dos activities, una para Runnable y otra para las llamadas de Message.
  • Dos objetos HandlerThread :
    • WorkerThread para recibir y procesar llamadas desde el UI.
    • CounterThread para recibir las llamadas de Message desde el WorkerThread.
  • Algunas clases de utilidad (para preservar objetos durante cambios en la configuración y de layout)

2. Envío y Recepción de Runnables

Comencemos experimentando con el método Handler.post(Runnable) y sus variantes que añaden un runnable al MessageQueue asociado a un thread. Creamos una activity denominada RunnableActivity la cual se comunica con un thread en background denominado WorkerThread .

La RunnableActivity instancia un thread en background denominado WorkerThread pasándole un Handler y un WorkerThread.Callback como parámetros. La activity puede hacer llamadas a WorkerThread de forma asíncrona para descargar un bitmap y mostrar un toast en un determinado momento. Los resultados de las tareas realizadas por el worker se pasan a la activity RunnableActivity por medio de runnables enviados al Handler recibido por WorkerThread.

2.1 Preparando un Handler para RunnableActivity

En la activity RunnableActivity crearemos un Handler que se pasará al WorkerThread El uiHandler se asociará con el Looper desde el UI thread, dado que está siendo invocado desde ese thread.

2.2 Declarando WorkerThread y su interfaz Callback

El WorkerThread es un thread en background donde iniciaremos diferentes tipos de tareas. Se comunica con la interfaz de usuario por medio de responseHandler y una interfaz callback que recibió durante su instanciación. Las referencias recibidas desde las activities son del tipo WeakReference<> , dado que una activity podría ser destruida y su referencia perdida.

La clase ofrece una interfaz que puede ser implementada por el UI. También extiende HandlerThread, una clase de ayuda construida sobre Thread que en realidad contiene un Looper y una cola MessageQueue. Desde ahora ya está correctamente configurado el framework HaMeR.

2.3 Inicializando WorkerThread

Necesitamos añadir un método a WorkerThread que será invocado por las activities que preparan para su uso el postHandler del thread. El método debe ser invocado solo una vez que el thread se haya iniciado.

En RunnableActivity debemos implementar WorkerThread.Callback e inicializar el thread de modo que pueda uitilizarse.

2.4 Utilizando Handler.post() en el WorkerThread

El método WorkerThread.downloadWithRunnable() descarga un bitmap y lo envia a la activity RunnableActivity para ser mostrado en un ImageView. Se ilustran los dos usos básicos de la instrucción Handler.post(Runnable run):

  • Para permitir enviar un objeto Runnable a una cola MessageQueue asociada consigo mismo cuando el método post() es invocado desde un Handler asociado al Looper del Thread.
  • Para permitir la comunicación con otros Threads cuando el método .post() es invocado desde un Handler asociado al Looper de otro Thread.
  1. El método WorkerThread.downloadWithRunnable() envia un Runnable a la cola MessageQueue del WorkerThread utilizando postHandler, un Handler asociado con el Looper de WorkThread
  2. Cuando el runnable es procesado, se descarga un Bitmap en WorkerThread
  3. Una vez descargado el bitmap, se usa un handler asociado con el Thread UI de nombre responseHandler, para enviar n runnable a la activity RunnableActivity que contiene el bitmap.
  4. El runnable es procesado y WorkerThread.Callback.loadImage se utiliza para mostrar la imagen en un ImageView

2.5 Uso de Handler.postAtTime() y de Activity.runOnUiThread()

El método WorkerThread.toastAtTime() planifica una tarea para ser ejecutada en un momento concreto, mostrando al usuario un Toast. El método ilustra el uso de Handler.postAtTime() y de Activity.runOnUiThread()

  • Handler.postAtTime(Runnable run, long uptimeMillis) envia un runnable en un momento concreto.
  • Activity.runOnUiThread(Runnable run) utiliza el el handler por defecto de UI para enviar un runnable al main thread.

3. Envío de Mensajes con MessageActivity y WorkerThread

A continuación vamos a explorar algunas de las diferentes formas de utilizar MessageActivity para enviar y procesar objetos Message La activity MessageActivity instancia WorkerThread pasando un Handler como parámetro. WorkerThread tiene sus tareas expuestas a través de unos métodos públicos que se deben de invocar desde la activity para poder descargar el bitmap, descargar un bitmap aleatorio o mostrar un Toast después de un lapso de tiempo concreto. Los resultados de todas estas operaciones son enviadas de vuelta a MessageActivity usando objetos Message enviados por el responseHandler

3.1 Prepara la respuesta del Handler desde MessageActivity

Como en RunnableActivity, en MessageActivity nosotros instanciamos e inicializamos un WorkerThread enviando un Handler para recibir los datos desde el thread en background. Sin embargo en esta ocasión no implementamos WorkerThread.Callback ; en su lugar recibimos desde WorkerThread por medio de objetos Message

Ya que el código de MessageActivity y RunnableActivity es básicamente el mismo, nos centramos solo en uiHandler, el cual será enviado al WorkerThread para recibir los mensajes desde él.

Primero proveemos algunas claves int para ser usadas como identificadores de los objetos Message.

En la implementación de MessageHandler , extenderemos Handler e implementamos el método handleMessage(Message) donde todos los mensajes serán procesados. Hay que destacar aquí que usamos Message.what para identificar el mensaje y que se obtiene diferentes tipos de datos de Message.obj Vamos a revisar rápido lo mas importante de las propiedades de Message antes de profundizar en el código.

  • Message.what : int identifica el Message
  • Message.arg1 : int argumento arbitrario
  • Message.arg2 : int argumento arbitrario
  • Message.obj : Object para almacenar diferentes tipos de datos

3.2 Enviar mensajes con WorkerThread

Vamos a volver atrás a la clase WorkerThread. Añadiremos algo de código para descargar un bitmap específico y algo de código para descargar uno aleatorio. Para acometer estas tareas enviaremos objetos Message desde WorkerThread a sí mismo y enviamos los resultados de vuelta a MessageActivity usando exactamente la misma lógica aplicada anteriormente para RunnableActivity.

Primero tenemos que extender Handler para procesar los mensajes descargados.

El método downloadImageMSG(String url) es básicamente el mismo que downloadImage(String url) La única diferencia es que el primero envía el bitmap descargado de vuelta a UI usando por medio de un mensaje usando para ello responseHandler.

loadImageOnUIMSG(Bitmap image) es el encargado de enviar el mensaje con el bitmap descargado a MessageActivity

Hay que destacar que en vez de crear un objeto Message desde cero, usamos el método Handler.obtainMessage(int what, Object obj) para recuperar un Message del pool global, ahorrando así algunos recursos. Destacar también que estamos invocando obtainMessage() en responseHandler obteniendo un Message asociado con el Looper de MessageActivity. Hay dos formas de recuperar un Message desde el pool global: Message.obtain() y Handler.obtainMessage()

Lo único que queda por hacer en la tarea de descargar la imagen es proveer los métodos de envío de un Message a WorkerThread para comenzar el proceso de descarga. Obsérvese que en esta ocasión llamamos la método Message.obtain(Handler handler, int what, Object obj) de handlerMsgImgDownloader, asociando así el mensaje con el looper de WorkerThread

Otra posibilidad interesante es el envío de objetos Message para ser procesados con un cierto lapso de tiempo de retraso a través de la sentencia Message.sendMessageDelayed(Message msg, long timeMillis)

Hemos creado un Handler expresamente para el envío de mensajes con retraso. En lugar de extender la clase Handler, tomamos la alternativa de crear instancias de un Handler mediante la interfaz Handler.Callback, para lo cual implementamos el método handleMessage(Message msg) para procesar el mensaje con retraso.

4. Conclusión

Ya has visto suficiente código para entender cómo aplicar los conceptos básicos del framework HaMeR para manejar concurrencia en Android. Hay algunas otras características interesantes del proyecto final almacenada en GitHub, y recomendamos encarecidamente que le eches un vistazo.

Por último, tengo algunas consideraciones que debes tener en cuenta:

  • No olvides tener en cuenta el ciclo de vida de una Activity de Android cuando se trabaja con HaMeR y Threads en general. De lo contrario, tu aplicación puede fallar cuando el thread intenta obtener acceso a las Activities que han sido destruidas debido a los cambios de configuración o por otras razones. Una solución común es utilizar un RetainedFragment para almacenar el hilo y rellenar el Thread en background con referencia a la Activity cada vez que la Activity es destruida. Mira la solución en el proyecto en GitHub.
  • Las tareas que se ejecutan debido a objetos Runnable y Message objetos procesados en los Handlers no se ejecutan de forma asíncrona. Ellas se ejecutan sincrónicamente en el thread asociado con el handler. Para que sea asincrónica, necesitarás crear otro thread, enviar, después el objeto Runnable / Message en él y recibir los resultados en el momento adecuado.

Como se puede ver, el framework HaMeR tiene un montón de posibilidades diferentes, y es una solución bastante abierta con un montón de opciones para el manejo de concurrencia en Android. Estas características pueden ser ventajas sobre AsyncTask, dependiendo de sus necesidades. Explora más del framework, lee la documentación y crearás grandes cosas con él.

¡Nos vemos pronto!

Advertisement
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.