Threanding en Android

Multi-threadin es uno de los conceptos más importantes den cualquier lenguaje de porgamación. Con un uso adecuado de los threads en Android, podremos mejorar mucho el rendimiento de nuestras aplicaciones. Cuando un usuario abre una aplicación, Android crea un proceso linux para esa aplicación. Además, el sistema crea un hilo de ejecución (thread) para ese proceso, llamado main-thread, thread principal o UI thread (user interface thread).

El main thread no es más que un handler thread, que veremos más adelante cuando tratemos los handlers. El main thread es el responsable de gestionar todos los eventos de la aplicación, como callbaks asociados con el ciclo de vida de las actividades y fragments así como los eventos de entrada del usuario y de otras aplicaciones. Además gestiona las tareas de renderizado o dibujado de las pantallas, transiciones, animaciones, etc.

Cualquier bloque de código que necesita ser ejecutado de introduce en una cola de trabajos asociada al thread, y por tanto servida o ejecutada por el main thread cuando corresponda, una tras otra de manera secuencial o síncrona. Como el main thread tiene mucho trabajo, es mejor que las tareas que van a tardar tiempo en completarse sean tratadas por otros threads y dejar al main thread que continúe con sus tareas de interfaz de usuario y atención a los callbaks.

Es esencial evitar usar el main thread para realizar cualquier operación que pueda acabar bloqueada, lo que ocasionaría que no podría atender al resto de eventos de usuario ni callbacks, con lo que la aplicación entera se bloquearía. Las operaciones de red, codificación/decodificación de imágenes, accesos a bases de datos, o cargas de algunos componentes o ficheros son algunos ejemplos de este tipo de operaciones que, aunque no se bloqueen, pueden tardar tiempo en completarse, dando una sensación de poco rendimiento de la aplicación, es decir, una experiencia de usuario no deseada, puesto que durante ese tiempo se queda “colgada” desde el punto de vista del usuario.

Si el UI Thread se bloquea por más de 5 segundos el sistema mostrará un cuadro de dialogo al usuario “La aplicación no responde” para que el usuario decida si quiere esperar o detener la aplicación. Para evitar esta situación, estas operaciones son habitualmente ejecutadas en un thread a parte o nuevo creado a tal fin. De esta forma la ejecución de estas tareas pasa a ser asíncrona con el UI (user interface).

Android proporciona varias maneras de crear y gestionar threads, veremos algunas de las que vienen con el sistema operativo, habiendo también muchas librerías que permiten también gestionar threads. Como decimos hay varias formas de trabajar con threads y resulta importante conocerlas para seleccionar la más apropiada en función de la tarea y sus características a realizar.

Una consideración importante a tener en cuenta cuando trabajamos multi-thread es que no se permite manipular el interfaz de usuario desde otros hilos de ejecución. Esto quiere decir que desde otro hilo en ejecución, no tengo acceso a las vistas de los layouts de las activities o fragments, y por tanto no puedo hacer cosas como actualizar una barra de progreso, modificar textos o indicaciones al usuario, etc. Veremos como lidiar con este aspecto.

Vamos a explorar, en varios artículos, los siguientes aspectos:

  • Threads
  • Handlers
  • Queues
  • Handler-Threads
  • Asynck Tasks

Las aplicaciones también pueden requerir que se ejecuten algunas tareas incluso cuando el usuario no usa la app de forma activa, por ejemplo, para hacer una sincronización periódica con un servidor en segundo plano o una búsqueda frecuente de contenido nuevo dentro de una app. También pueden requerir que los servicios se ejecuten inmediatamente hasta completarse incluso después de que el usuario haya terminado de interactuar con la app.

Cuando trabajamos con varios hilos, estos pueden acceder a las variables de forma simultánea. Hay que tener cuidado de que un hilo no modifique el valor de una variable mientras otro hilo está leyéndola. Este problema se resuelve en Java definiendo secciones críticas mediante la palabra reservada synchronized. Trataremos este problema más adelante.

Respecto a las operaciones que se ejecutan en segundo plano sin atención del usuario, o cuando la aplicación no tiene el foco UI, hay que tener en cuenta las restricciones de ejecución, o modo descanso, que impone Android a estas tareas, para seleccionar cómo diseñarlas para que puedan funcionar como esperamos. En relación a esto, Android restringe la actividad en segundo plano cuando la app (o una notificación de servicio en primer plano) no está visible para el usuario.

  • Android 6.0 (API nivel 23) introdujo el modo Descanso y App Standby. El modo Descanso restringe el comportamiento de la app cuando la pantalla está apagada y el dispositivo no se mueve. App Standby coloca las aplicaciones no utilizadas en un estado especial que restringe el acceso a la red, los trabajos y las sincronizaciones.
  • Android 7.0 (API nivel 24) limitó las transmisiones implícitas e introdujo Descansa, estés donde estés.
  • Android 8.0 (API nivel 26) limitó aún más el comportamiento en segundo plano, que incluyó la obtención de la ubicación en segundo plano y la liberación de bloqueos de activación almacenados en caché.
  • Android 9 (API nivel 28) introdujo Intervalos de App Standby, en los que las solicitudes de recursos de la app se priorizan de forma dinámica según los patrones de uso de la aplicación.

Es importante comprender las necesidades de tu tarea y elegir la solución correcta que cumpla con las prácticas recomendadas del sistema para programar tu trabajo en segundo plano.

Cómo elegir la solución adecuada para tu trabajo

  • ¿El trabajo se puede posponer o debe realizarse de inmediato? Por ejemplo, si necesitas obtener datos de la red en respuesta al clic del usuario en un botón, ese trabajo debe realizarse de inmediato. Sin embargo, si deseas subir tus registros al servidor, ese trabajo puede posponerse sin perjudicar el rendimiento de la app ni las expectativas de los usuarios.
  • ¿El trabajo depende de las condiciones del sistema? Tal vez quieras que tu trabajo se ejecute solamente cuando el dispositivo cumpla con ciertas condiciones, como estar conectado a la alimentación o a Internet, etc. Por ejemplo, es posible que tu app necesite comprimir de forma periódica sus datos almacenados. Para evitar que el usuario se vea afectado, es recomendable que este trabajo se realice únicamente cuando el dispositivo se esté cargando y esté inactivo.
  • ¿El trabajo debe ejecutarse en un momento preciso? Una app de calendario puede permitir que el usuario configure un recordatorio para un evento en una fecha y una hora específicas. El usuario espera ver la notificación del recordatorio en el momento correcto. En otros casos, es posible que la app no tenga en cuenta el momento preciso en el que se ejecuta el trabajo. La app podría tener requisitos generales (como “primero debe ejecutarse el trabajo A; segundo, el trabajo B; y tercero, el trabajo C”), pero no requerir que los trabajos se ejecuten en un momento específico.

…. más información en Guía para el procesamiento en segundo plano