Android: Threading indoloro

[Fuente: http://developer.android.com/resources/articles/painless-threading.html]

Este articulo trata sobre el modelo de hilos utilizado por las aplicaciones Android y cómo las aplicaciones pueden asegurar un mejor rendimiento en sus interfaces gráficas utilizando threads para procesar las operaciones pesadas, más que hacerlo en el main thread. También se explica el API que tu aplicación puede utilizar para interactuar con los componentes del UI que se ejecutan en el main thread y que puedan ser modificados por los otros threads.

El hilo del UI

Cuando arrancamos una aplicación, el sistema crea el llamado thread “main” de la aplicación. El main thread, también llamado el UI thread, es muy importante porque se encarga de despachar los eventos a los widgets apropiados, incluyendo eventos de dibujo. Es también el thread donde tus aplicaciones interactuan con los componentes en ejecución del Android UI Toolkit.

Por ejemplo, si tocamos el botón de la “a” en la pantalla, el UI thread despacha el evento touch al widget, que pondrá su estado en “presionado” y encolará una invalidate request a la cola de eventos. Es el UI thread el que saca de la cola la request y notifica al widget para que se redibuje a si mismo.

Este modelo de single-thread puede llevar a un rendimiento malo a menos que tu aplicación sea implementada apropiadamente. Especificamente, si todo esta sucediendo en un solo thread, realizar operaciones pesadas tales como acceso a la red o queries de bases de datos en el UI thread puede bloquear por completo el interfaz del usuario. Tendremos que ningun evento puede ser despachado, incluyendo eventos de dibujo, mientras se este ejecutando una operación pesada. Desde la perspectiva del usuario, la aplicación aparece colgada. Incluso peor , si el UI thread se bloquea por más de unos pocos segundos (sobre 5 segundos actualmente) al usuario se le aparece el dialogo de “application not responding”.

Si quiere ver cómo de feo esto puede ser, escribe una simple aplicación con un botón que invoque Thread.sleep(2000) in its OnClickListener. El botón permanecerá en su estado presionado por unos 2 segundos antes de volver a su estado normal. Cuando esto sucede, es muy fácil para el usuario precibir que esa aplicación es muy lenta.

Para resumir, es vital para la responsividad del UI de tu aplicación mantener el UI thread desbloqueado. Si tienes operaciones pesadas que realizar , debemos estar seguros de hacerlas en otros thread (que llamaremos background or worker threads).

Veamos un ejemplo de un click listener descargando una imagen de la red y mostrandola en un ImageView:

public void onClick(View v) {
  new Thread(new Runnable() {
    public void run() {
      Bitmap b = loadImageFromNetwork();
      mImageView.setImageBitmap(b);
    }
  }).start();
}

A primera vista, este código parece ser una buena solución a tu problema , no se bloquea el UI thread. Desafortunadamente, viola al modelo single-threaded para el UI: el Android UI toolkit no es thread safe y debe siempre ser manipulado en el UI thread. En la pieza de código anterior, el ImageView es manipulado en el worker thread, lo cual puede causar problemas bastantes complejos.Encontrar estos errores puede ser dificil y llevar bastante tiempo.

Android ofrece varias formas de acceder al UI thread desde otros thread. Esta la lista de ellos:

Puedes utilizar cualquier de estas clases y métodos para corregir el ejemplo del código previo:

public void onClick(View v) {

  new Thread(new Runnable() {
    public void run() {
      final Bitmap b = loadImageFromNetwork();
      mImageView.post(new Runnable() {
        public void run() {
          mImageView.setImageBitmap(b);
        }
      });
    }
  }).start();
}

Desafortunadamente, estas clases y métodos pueden tender a hacer tu código más cimplicado y más dificil de leer. Se convierte incluso peor cuando implementas operaciones complejas que requieren actualizaciones del UI frecuentemente.

Para remediar este problema, Android 1.5 u plataformas posteriores ofrecen una clase de utilidad llamada AsyncTask, que simplifica la creación de tareas long-running que necesitan comunicar con el interface de usuario.Hay un equivalente para AsyncTask en Android 1.0 y 1.1 , es la clase UserTask. ofrece el mismo API.

El objetivo de AsyncTask es gestionar el hilo por ti. Nuestro ejemplo previo quedaria de la sigueinte forma:

public void onClick(View v) {
  new DownloadImageTask().execute("http://example.com/image.png");
}

private class DownloadImageTask extends AsyncTask<String, Void, Bitmap> {
     protected Bitmap doInBackground(String... urls) {
         return loadImageFromNetwork(urls[0]);
     }

     protected void onPostExecute(Bitmap result) {
         mImageView.setImageBitmap(result);
     }
 }

Como vemos m AsynTask hay que utilizarlo haciendo una subclase. Es también muy importante recordar que una instancia de AsyncTask debe ser creada en el UI thread y puede ser ejecutada solo una vez. Para más info AsyncTask documentation si quieres un entendimiento completo de su uso, pero mostraremos un vista rápida aqui:

Además de la documentación oficial, puedes leer varios ejemplos complejos en el código fuente del Shelves (ShelvesActivity.java and AddBookActivity.java) and Photostream (LoginActivity.javaPhotostreamActivity.java andViewPhotoActivity.java). Recomendamos encarecidamente leer el código fuente del Shelves para ver como hacer persistir las tareas entre cambios en la configuración y cómo cancelarlas apropiadamente cuando la activity se destruye.

Independientemente de si utilizas o no AsyncTask , siempre recuerda estas dos reglas sobre el single thread model:

  1. No bloquees el UI thread
  2. Asegurate que tu accedes al Android UI Toolkit solo desde el UI thread

Con AsyncTask es mucho más fácil hacer ambas cosas.