Advertisement
  1. Code
  2. Android SDK

Entendiendo la Concurrencia en Android mediante HaMeR

Scroll to top
Read Time: 12 min

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

1. Introducción

Todo aquel que desarrolla en Android descubre la importancia de la concurrencia. La única forma de crear una aplicación responsive es dejar el Thread del UI tan libre como sea posible, permitiendo que todo el trabajo duro sea realizado por Threads de forma asíncrona en el Background.

Debido al diseño de Android, gestionar threads empleando solo los paquetes java.lang.thread y java.util.concurrent puede resultar realmente complicado. Utilizar los paquetes de threading a bajo nivel de Android significa tener que preocuparse de un montón de sincronización compleja para evitar las condiciones de carrera. Afortunadamente los colegas de Google hicieron el trabajo duro y construyeron algunas herramientas magnificas que facilitan nuestros trabajos: AsyncTaskIntentService , Loader, AsyncQueryHandler y CursorLoader son todas ellas útiles al igual que también lo son Handler, Message y Runnable Hay muchas buenas opciones para elegir, cada una de ellas con sus pros y contras.

Se ha dicho mucho sobre el objeto AsyncTask , y mucha gente lo utiliza a modo de bala de plata como una solución a la concurrencia en Android. Es extremadamente útil para operaciones breves, fáciles de implementar y probablemente la aproximación mas popular para la concurrencia en Android. Si deseas aprender mas acerca de AsyncTask, visita los siguientes posts de Envato Tuts+.

Sin embargo AsyncTask  no debería ser la única herramienta de tu cinturón de herramientas.

Para operaciones de larga duración, para problemas de concurrencia complejos, o para conseguir mejor eficiencia en algunas situaciones, tu deberías elegir otra solución. Si necesitas mayor eficiencia o mas flexibilidad de la que AsyncTask ofrece, deberías de utilizar el framework HaMeR (Handler , Message & Runnable). En este tutorial exploraremos el framework HaMeR, uno de los mas poderosos modelos de concurrencia de los disponibles en Android y aprenderemos cuando y cómo utilizarlo. En un próximo tutorial mostraré como programar en una aplicación algunas de las posibilidades de HaMeR.

La siguiente sección presenta la importancia de los hilos (threads) de background en el sistema Android. Si estas familiarizado con este concepto, eres libre de saltarlo e ir directamente a la presentación del framework HaMeR en la sección 3.

2. Responsabilidad por medio de Background Threads.

Cuando una aplicación Android se inicia, el primer thread generado por su proceso es el main thread, también conocido como  UI Thread, el cual es responsable de manejar toda la lógica de la interfaz de usuario. Este es el thread mas importante de una aplicación. Es el responsable de manejar toda la interacción de usuario y también enlaza juntas todas las partes móviles de la aplicación.  Android se toma esto muy en serio, y si tu UI Thread se atasca trabajando en una tarea durante mas de unos segundos, la aplicación romperá.

[El UI Thread] es muy importante porque tiene por cometido despachar los eventos a los elementos apropiados del interfaz de usuario, incluidos los eventos para representar dichos elementos. Es también el thread sobre el cual la aplicación interactúa con componentes  desde el conjunto de herramientas Android UI (componentes de los paquetes android.widget y android.view). Por ello el main thread es denominado a veces UI thread. Procesos e Hilos, Android Developer Guide

El problema es que casi todo el código de una aplicación Android será ejecutado  por defecto en el  UI Thread. Ya que las tareas en un thread se llevan a cabo de forma secuencial, esto podría causar que la interfaz de usuario se "congelase", haciendo que ésta no respondiese mientras a la vez se estuviese procesando algún otro trabajo.

Leave the UI Thread as free as possible using Background ThreadsLeave the UI Thread as free as possible using Background ThreadsLeave the UI Thread as free as possible using Background Threads

Las tareas de larga duración invocadas en el UI serán con toda seguridad fatales para tu aplicación, y aparecerá un diálogo ANR (la Aplicación No Responde).  Incluso las tareas pequeñas pueden comprometer la experiencia de usuario, por lo tanto la aproximación correcta es eliminar del UI Thread tanto trabajo como sea posible por medio de background threads. Como dije anteriormente, hay muchas formas de resolver una cuestión, y nosotros exploraremos el framework HaMeR, una de las soluciones nucleares que Android proporciona para manejar esta situación. 

3. El framework HaMeR

El framework HaMeR permite a los threads en background enviar mensajes o postear runnables en el UI thread y también a cualquier otro MessageQueue de un thread por medio de handlers. HaMeR hace referencia a Handler, Message & Runnable. Hay también otras clases importantes que operan junto con HaMeR: Looper y MessageQueue. Juntos, estos objetos son responsables de facilitar la gestión de hilos (threading) en Android, considerando la sincronización y proveyendo de métodos fáciles para que los threads en background se comuniquen con el UI thread así como con otros threads.

Así es como se conjugan las clases del framework HaMeR.

The HaMeR frameworkThe HaMeR frameworkThe HaMeR framework
  • Looper ejecuta un bucle de mensajes en un thread empleando MessageQueue.
  • MessageQueue contiene una lista de mensajes para ser despachados por el Looper.
  • Handler permite el envio y procesado de Message y Runnable al MessageQueue. Puede ser empleado para procesar mensajes entre threads.
  • Message contiene una descripción y datos que pueden ser enviados a un handler.
  • Runnable representa una tarea que ha de ser ejecutada.

Con el framework HaMeR, los threads pueden enviar mensajes o postear objetos runnables tanto a si mismos como al UI thread.  HaMeR también promueve la interacción con los thread en background pro medio de Handler.

3.1 La clase Handler

Handler es el elemento principal de HaMeR. Es el responsable de enviar objetos Message (mensaje de datos) y postear objetos Runnable (mensaje de tarea)  al MessageQueue asociado con un Thread .  Tras enviar las tareas a la cola, el handler (manejador) recibe los objetos desde el Looper y procesa los mensajes en el momento apropiado utilizando el Handler asociado con él.

Un Handler puede ser utilizado para enviar o postear objetos Message y Runnable entre threads, siempre y cuando los threads compartan el mismo proceso. De lo contrario sería necesario crear un IPC (Proceso Interno de Comunicación), una metodología que supera el alcance de este tutorial.

Instanciando un Handler

Un Handler debe estar siempre asociado con un Looper, y esta conexión necesita establecerse durante su instanciación. Si no se le proporciona un Looper al Handler, se le asignará el Looper del Thread actual.

1
// Handler uses current Thread's Looper

2
Handler handler = new Handler();
3
4
// Handler uses the Looper provides

5
Handler handler = new Handler(Looper);

Hay que tener en cuenta que un Handler está siempre asociado con un Looper, y que ésta conexión es permanente y no puede ser modificada una vez que esté establecida. Sin embargo un thread de un Looper puede estar asociado con multiples Handler . También es importante destacar que un Looper debe estar activo previamente a su asociación con un Handler.

3.2. Looper y MessageQueue

El trabajo cooperativo entre Looper y MessageQueue en un hilo Java crea un bucle de tareas que son procesadas secuencialmente. Dicho bucle se mantendrá vivo mientras espera a recibir más tareas. Un thread puede tener solo un Looper y un MessageQueue asociado con él; sin embargo, puede haber multiples handlers para cada thread. Los handlers son responsables de procesar las tareas en la cola y cada tarea sabe cual es el handler responsable de su procesamiento.

3.3. Preparando un Thread para HaMeR

El UI o main thread es el único tipo de thread que por defecto ya dispone de un Handler, un Looper y un MessageQueue. Otros threads deben ser preparados con esos objetos antes de que puedan trabajar con el framework HaMeR. Primero necesitamos crear un Looper que ya contenga un MessageQueue y adjuntarlo al thread. Se puede hacer esto por medio de una subclase de Thread, como sigue.

1
// Preparing a Thread for HaMeR

2
class LooperThread extends Thread {
3
      public Handler mHandler;
4
      public void run() {
5
          // adding and preparing the Looper

6
          Looper.prepare();
7
          // the Handler instance will be associated with Thread’s Looper

8
          mHandler = new Handler() {
9
              public void handleMessage(Message msg) {
10
                  // process incoming messages here

11
              }
12
          };
13
          // Starting the message queue loop using the Looper

14
          Looper.loop();
15
      }
16
  }

Sin embargo es mas directo emplear una clase de ayuda denominada HandlerThread que contiene un Looper y un MessageQueue construido en un Thread de Java y está preparado para recibir un Handler.

1
// The HandlerThread class includes a working Looper

2
public class HamerThread extends HandlerThread {
3
    // you just need to add the Handler

4
    private Handler handler;
5
    public HamerThread(String name) {
6
        super(name);
7
    }
8
}

4. Posteando Runnables

Runnable es una interfaz Java que tiene muchos usos. Puede ser interpretado como una tarea individual para ser llevada a cabo en un Thread. Tiene un único método para ser implementado, Runnable.run()  para llevar a cabo la tarea.

1
// Declaring a Runnable

2
Runnable r = new Runnable() {
3
     @Override
4
     public void run() {
5
          // the task goes here

6
     }
7
};

Hay multiples opciones de postear un Runnable en un Handler.

  • Handler.post(Runnable r) : añade el Runnable a MessageQueue
  • Handler.postAtFrontOfQueue(Runnable r) : añade el Runnable al inicio de la cola MessageQueue
  • Handler.postAtTime(Runnable r, long timeMillis): añade el Runnable a la cola MessageQueue para ser invocado en un momento concreto.
  • Handler.postDelayed(Runnable r, long delay): añade el  Runnable  para ser invocado después de un periodo de tiempo concreto.
1
// posting a Runnable on a Handler

2
Handler handler = new Handler();
3
handler.post(
4
     new Runnable() {
5
          @Override
6
          public void run() {
7
               // task goes here

8
          }
9
     });

Es posible utilizar el handler UI por defecto para postear un Runnable invocando a Activity.runOnUiThread().

1
// posting Runnable using the UI Handler

2
Activity.runOnUiThread(
3
    new Runnable() {
4
        @Override
5
        public void run(){
6
            // task to perform

7
        }
8
    });

Es importante tener presente varias cosas acerca de los Runnable. Al contrario que un Message, un Runnable no puede reciclarse una vez el trabajo se ha realizado, está muerto. Como forma parte del paquete estándar de Java, un Runnable no depende de Handler y puede ser invocado desde un Thread estándar utilizando Runnable.run() método. Sin embargo ésta aproximación no tiene nada que hacer con el framework HaMeR y no comparte ninguna de sus ventajas.

5. Envio de Mensajes

El objeto Message define un mensaje que contiene una descripción y algún dato arbitrario que puede ser enviado y procesado por medio del Handler. El Message es identificado con un int definido en Message.what(). El Message puede contener otros dos int como argumentos y un Object para almacenar diferentes tipos de datos.

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

Cuando se necesita enviar un mensaje, en lugar de crear uno desde cero, la recomendación es recuperar uno reciclado directamente del pool global por medio de las ordenes Message.obtain() o Handler.obtainMessage() .        Hay diferentes versiones de esos métodos que permiten obtener un Message acorde con lo que se necesite.

Un uso común de Handler.obtainMessage() es cuando se necesita enviar un mensaje al thread en background. Se utiliza el Handler asociado con el Looper de ese thread para obtener un Message y enviarlo al thread en background, como en el ejemplo que sigue.

1
int what = 0;
2
String hello = "Hello!";
3
// Obtaining Message associated with background Thread

4
Message msg = handlerBGThread.obtainMessage(what, hello);
5
// Sending the Message to background Thread

6
handlerBGThread.sendMessage(msg);

Hay muchos métodos interesantes en la clase Message, y mi consejo es que le eches un vistazo a la documentación.

5.1. Opciones con sendMessage() 

De la misma forma que podemos postear Runnable s , hay multiples opciones para enviar Message s .

  • Handler.sendMessage( Message msg ) : añadir un Message a la cola MessageQueue
  • Handler.sendMessageAtFrontOfQueue( Message msg ) : añadir un Message al inicio de la cola MessageQueue .
  • Handler.sendMessageAtTime( Message msg, long timeInMillis ) : añadir un Message a la cola en un momento concreto.
  • Handler.sendMessageDelayed ( Message msg, long timeInMillis ) : añadir un Message a la cola después de haber pasado una cantidad de tiempo concreta.

5.2. Gestión de mensajes con Handler

Los objetos Message enviados por Looper son procesados por el handler por medio del método Handler.handleMessage . Todo lo que hay que hacer es extender la clase Handler y sobreescribir este método para procesar los mensajes.

1
public class MessageHandler extends Handler {
2
        @Override
3
        public void handleMessage(Message msg) {
4
            switch (msg.what) {
5
                // handle 'Hello' msg

6
                case 0:{
7
                    String hello = (String) msg.obj;
8
                    System.out.println(hello);
9
                    break;
10
                }
11
            }
12
        }
13
    }

6.Conclusión

El framework HaMeR puede ayudar a mejorar el código de concurrencia de tu aplicación. Puede parecer algo confuso de un principio cuando se compara con la simplicidad de AsyncTask, pero la apertura que ofrece HaMeR puede suponer una ventaja, si se emplea correctamente.

Recuerda:

  • Handler.post() estos métodos son empleados cuando los enviadores conocen que operaciones llevar a cabo. 
  • Handler.sendMessage() estos métodos se utilizan cuando el receptor conoce qué operación llevar a cabo.

Para aprender mas acerca de threading en Android, puede que te resulte de interés el libro Efficient Android Threading: Asynchronous Processing Techniques for Android Applications de Anders Goransson.

6.1. A continuación?

En el próximo tutorial continuaremos explorando el framework HaMeR con un enfoque práctico, construyendo una aplicación que demuestre diferentes formas de utilizar éste framework de concurrencia de Android. Crearemos ésta aplicación desde el inicio, intentando diferentes posibilidades como la comunicación entre Threads , comunicación con el thread de UI , así como el envío de mensajes y posteo de Runnable s con un retardo. 

Nos vemos pronto !

Advertisement
Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
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.