Aprende Android en 20 conceptos: Conceptos 11 y 12
La semana pasada llegamos al ecuador de esta sección. Hoy nos toca empezar la segunda mitad del mismo, centrándonos en uno de los elementos más importantes en Android: la ejecución de tareas en segundo plano.
Para ello, veremos la definición de servicio. Pero en el caso de que nuestro servicio en segundo plano necesite mostrarnos resultados en la interfaz, analizaremos una clase específica: AsyncTask.
11. Servicios (La clase Service)
Cuando queremos escuchar música, administrar transacciones con la red o todo este tipo de acciones que llevan tiempo y van en segundo plano mientras hacemos otra cosa, debemos utilizar el concepto de servicio.
Un servicio es uno de los componentes de la aplicación, el cual nos permite realizar operaciones de larga duración en segundo plano, sin requerir una interfaz de usuario. Un servicio no tiene por qué depender de que una aplicación esté en primer plano o no.
Hay dos formas de gestionar un servicio:
- Empezado: el método startService nos permite que corra en segundo plano indefinidamente (incluso cuando destrozamos el componente que lo empezó.
- Ligado: el método bindService ofrece una interfaz cliente-servidor que permite a los componentes interactuar con el servicio, enviar peticiones, obtener resultados, y realizar procesos con comunicacion interprocesal (IPC).
Pero hay que tener cuidado, pues el servicio se ejecutará en la hebra principal de su proceso padre, por lo que si vamos a hacer un trabajo intensivo en la CPU, deberíamos crear una nueva hebra.
¿Cuáles son los principios básicos de los servicios? Los siguientes:
- Extender la clase Service y sobreescribir métodos
- Método onStartCommand: Es llamado cuando otro componente solicita que el servicio empiece (startService). Pero es nuestra responsabilidad parar el servicio (stopSelf o stopService). Este método debe devolver uno de los siguientes valores:
- START_NOT_STICKY: Si el sistema matase al servicio, no se recreará el servicio a menos que haya aún Intents pendientes de ser procesados. Es la opción más segura para evitar que el servicio esté funcionando cuando no es necesario.
- START_STICKY: Recrea el servicio y llama a onStartCommand con un Intent null, pero no reenvía el último Intent.
- START_REDELIVER_INTENT: Recrea el servicio y llama a onStartCommand con el último Intent que fue liberado al servicio
- Método onBind: Es llamado cuando otro componente quiere asociarse con el servicio. Este método siempre debe ser implementado. En caso de que no queramos permitir asociaciones, basta con devolver null.
- Método onCreate: Llamado cuando el servicio es creado por primera vez.
- Método onDestroy: Llamado cuando el servicio no está siendo usado más y está siendo destruido. Sirve para limpiar recursos de memoria.
Dentro de su ciclo de vida, podemos distinguir dos fases:
- Ciclo de vida completo: Entre onCreate y onDestroy.
- Ciclo de vida activo: Entre onStartCommand/onBind y unBind (en el segundo caso). Para el primer caso no hay un método de finalización del ciclo de vida activo.
Como componente de una aplicación que es, debemos declararlo en el Manifest.
Para crear un servicio, aparte de la clase Service, podemos utilizar la clase IntentService, la cual es una subclase de Service que usa una hebra adicional para administrar todas las peticiones de comienzo, de una en una. Es la mejor opción si no necesitamos que el servicio administre múltiples peticiones simultáneamente.
Podremos enviar notificaciones al usuario, ya sea a través de Toast o de la barra de notificaciones.
Por último, tambien podremos ejecutar un servicio en primer plano:
- Será un servicio considerado a hacer algo que el usuario necesita siempre activo, por lo que no será un candidato para ser destruido por el sistema cuando haya problemas de memoria.
- Debe proveer una notificación en la barra de estado, bajo la cabecera Ongoing, lo que significa que la notificación no puede ser eliminada a menos que el servicio sea parado o eliminado.
- Se utilizan los métodos startForeground/stopForeground
En el siguiente link podrás ver algunos ejemplos de buen uso de los servicios.
12. Tareas asíncronas (La clase AsyncTask)
Todos los componentes de una aplicación corren en el mismo proceso, y la mayoría de las aplicaciones no deberían cambiar este comportamiento, aunque podemos hacerlo en el Manifest con la etiqueta android:process.
Hay 5 niveles de importancia para los procesos:
-
Procesos en primer plano (lo que el usuario está haciendo)
- Actividades que han pasado por onResume
- Servicios asociados
- Servicios en primer plano
- Callbacks de servicios
- Método onReceive de los BroadcastReceiver
-
Procesos visibles (aún afecta a lo que el usuario ve en pantalla)
- Actividades que no están en primer plano pero aún están visibles (onPause)
- Un servicio que esta ligado a una actividad visible
- Procesos de servicios (servicios en ejecución que no están en las otras categorías)
- Procesos en background (procesos que tienen una actividad que no es visible actualmente al usuario)
- Procesos vacíos (no tienen ningún componente de la aplicación, normalmente de caching).
Por otro lado están las hebras (Threads). Cuando la aplicación es lanzada, el sistema sólo crea una hebra principal para la interfaz de usuario (UI Thread). Pero, ¿qué ocurriría cuando queramos hacer un trabajo intenso? ¿Estaremos bloqueando la hebra de la interfaz de usuario? La respuesta es sí, y es por eso que para trabajos intensos debemos crear nuestras propias hebras.
Básicamente, hay dos reglas básicas:
- No bloquear la hebra de la interfaz de usuario o principal
- Si hiciéramos esto, estaríamos provocando el famoso error ANR (App Not Responding), el cual aparece cuando bloqueamos la interfaz gráfica por unos 5 segundos aproximadamente. Es el error más molesto de Android, y como desarrolladores es posible evitarlo siguiendo los consejos de Google.
- No acceder a la interfaz gráfica (Android UI Toolkit) desde fuera de la hebra de la interfaz gráfica. Para ello podemos hacer uso de:
- Método runOnUiThread(Runnable) de la clase Activity
- Método post(Runnable) de la clase View
- Método postDelayed(Runnable, long) de la clase View
Pero para simplificar aún más la posibilidad de realizar trabajos en segundo plano e ir actualizando la interfaz gráfica surge la clase AsyncTask, de modo que:
- Te permite hacer trabajo asíncrono en la interfaz gráfica
- Permite operaciones de bloqueo en una hebra secundaria
- Publica los resultados en la interfaz gráfica
Respecto al AsyncTask, hay que destacar que cuenta con:
- Hebra secundaria
- Método doInBackground: Será el que se ejecute en dicha hebra
- Hebra principal o de la interfaz gráfica. Los siguientes métodos se ejecutarán en dicha hebra:
- Método onPreExecute: Se ejecuta antes de que tenga lugar el método doInBackground
- Método onPogressUpdate: Se ejecuta cada vez que desde el método doInBackground queramos publicar el progreso con el método publishProgress
- Método onPostExecute: Se ejecuta al finalizar el método doInBackground
- La comunicación durante doInBackground:
- El método publishProgress se ejecutará desde la hebra secundaria, para notificar a la hebra principal que debe llevar a cabo la acción determinada en el método onProgressUpdate
- La comunicación a lo largo del ciclo de vida se realiza a través de parámetros.
Para iniciar un AsyncTask bastará hacer uso de su constructor y llamar al método execute, .pasándole todos los parámetros que queramos:
Personalmente, como desarrollador de Android, creo que me costaría vivir sin la clase AsyncTask, pues la considero tan relevante como lo puede ser la clase Activity.
Con todo esto, ya deberíamos ser capaces de ejecutar código en segundo plano, algo que resulta vital para el buen desarrollo de una aplicación, pues siempre debemos dar la impresión de que la interfaz gráfica está esperando el feedback del usuario y no que está bloqueada por algún trabajo en cuestión.
Poco a poco, vamos teniendo las herramientas más importantes para el buen desarrollo de una app. Espero que os esté resultando útil.
Ver la sección Aprende Android en 20 conceptos