Tareas de fondo en aplicaciones universales


Hace unos días @johnnyjosep me preguntó cómo podía hacer en una aplicación universal para descargar nuevo contenido cada cierto tiempo sin tener la aplicación abierta.

Sí, os debo la serie de artículos sobre el caché de HTTP, aunque si os fijáis bien en este hay un pequeño ejemplo de uso de la cabecera If-Modified-Since.

La forma más eficiente de notificar a la aplicación que algo remoto ha cambiado, es utilizar un sistema de notificaciones push, estilo Azure Notification Hubs, pero como esa opción tiene un coste, también tenemos otra posibilidad más económica (para nosotros): crear una Background Task con un temporizador, que compruebe desde el cliente si el archivo ha cambiado.

Añadir una tarea de fondo es muy sencillo, pero hay algunos aspectos que debemos tener en cuenta:

  • La definición de la tarea debe alojarse en una dll separada. En el ejemplo usaré una librería PCL para que funcione en ambos dispositivos. No sirve el proyecto Shared, porque ese proyecto no genera una librería sino que añade ese código directamente al ejecutable.
  • Es necesario solicitar permiso al usuario (en W8.1 aparece un diálogo, en WP8.1 es implícito al instalar la app)
  • Para evitar que nuestra tarea pierda el turno innecesariamente, hay que establecer condiciones, por ejemplo que sólo se ejecute al tener conexión a internet.
  • La tarea de fondo tiene que ser rápida, tenemos 2 segundos de procesador como máximo para realizar la tarea. Si queremos realizar una operación más larga tendremos que utilizar una tarea de tipo MaintenanceTrigger.
  • Si el tamaño de los archivos es grande deberíamos usar la API de BackgroundTransfer para que el sistema se encargue de la descarga y así evitamos el límite de 2 segundos de CPU de la tarea.
  • Concurrencia: en esta tarea descargamos un archivo que puede que ya tengamos y que estemos usando desde otro proceso (la app), así que tendremos que pedir la vez para acceder al mismo.

Ahora os cuento un poco más, pero para los inquietos aquí tenéis el código fuente.

Definición de la tarea

Una tarea de fondo debe implementar el interfaz IBackgroundTask, que cuenta con un solo método Run, es en ese método que se ejecuta nuestra tarea de fondo. Como tiene que estar en una .dll diferente y estamos desarrollando una Universal App, necesitaremos una biblioteca de clases “portable” (PCL) que podamos ejecutar en ambas plataformas. La solución nos quedará al final de esta forma: un proyecto por dispositivo con las pantallas, un proyecto Shared donde tendremos el ViewModel y un proyecto PCL con la tarea.

backgroundproject

En el método Run tendremos que ejecutar nuestra acción de descarga, pero como será una acción asíncrona necesitaremos pedir un aplazamiento. Así el sistema no cancelará nuestra tarea mientras espera a que llegue la respuesta del sitio web. Para pedir el aplazamiento llamamos al método GetDeferral, que debe ir acompañado de una llamada a Complete cuando hayamos acabado:

Para el ejemplo que le hice a @johnnyjosep escribí todo el método de descarga dentro de la misma tarea, pero como eso no le gustaba (eh, que sólo era un ejemplo rápido! ;D), he separado el control de descargas en otra clase, que básicamente se encarga de descargar el archivo y guardarlo en disco para más adelante:

Como observaréis tenemos un método público que devuelve una IAsyncActionWithProgress, para que sea compatible WinMD. Dentro genero una tarea anónima para poder hacer uso del bloque async/await que realiza la descarga del archivo, pero sólo si es necesario realizarla (más explicaciones sobre la respuesta 304 – NotModified en un próximo post).

También uso un semáforo para evitar que ambos procesos accedan al mismo archivo a la vez, pero eso también lo dejaremos para más adelante, podéis ver el código del semáforo dentro del ejemplo.

Para dar un poco de vida al LiveTile envío notificaciones al mismo para que veamos en la pantalla de inicio si hay alguna actividad (actualizando, contenido nuevo, contenido antiguo), aunque no toda la actividad será visible en Windows PHone, pues la información en el tile es más limitada:

Registar la tarea

Para que la tarea pueda ejecutarse sin la aplicación en marcha es importante que el usuario acepte que aparezca en la pantalla de bloqueo, por eso llamo al método BackgroundExecutionManager.RequestAccessAsync. En el método de registro configuramos una tarea de tipo TimeTrigger con una cadencia de 6 horas y una condición de ejecución SystemConditionType.InternetAvailable. Esto nos asegura que cada 6 horas y si la conexión a internet está disponible se ejecutará la tarea. Si no es posible ejecutarla y ya han pasado las 6 horas el sistema esperará a que la conexión a internet esté activa:

Si no hubiéramos puesto la condición la tarea se ejecutaría cada 6 horas, pero si no hay internet no tendremos una segunda oportunidad, simplemente fallará y tendremos que esperar otro ciclo más de 6 horas hasta que vuelva a ejecutarse.

Para que el registro funcione, necesitamos que el usuario acepte la condición de ejecución y como tiene interfaz de usuario sólo podremos ejecutar el registro cuando cargue el UI, por ejemplo en el evento Loaded de la página, aunque para una aplicación en producción deberíamos guardar la opción del usuario y no volverlo a pedir… o comprobar si el usuario lo ha deshabilitado y avisar de las consecuencias:

Basta de hablar, ¡dame el código ya!

El código completo del ejemplo está en github para que podáis jugar, viene con una página y un viewmodel que carga un feed. Como siempre, tened en cuenta que es sólo un ejemplo y faltan cosas para que sea código de producción.
mainpage

¿Y ahora qué?

Pues tenéis unas cuantas tareas por delante, como por ejemplo usar un BackgroundTransfer para que el sistema se encargue de las descargas, añadir un sistema de DI para que las clases fluyan, o mejorar la concurrencia con un método mejor que el AsyncSemaphore que me he creado para la tarea… y sobre todo contárnoslo después!

Happy tasking!

Anuncios

  1. Pingback: [Windows Phone 8.1] Update Task | Javier Suárez | Blog
  2. Pingback: [Windows Phone 8.1] Update Task - Javier Suárez
  3. Pingback: [Windows Phone 8.1] Reproducir Audio en Background | Javier Suárez | Blog
  4. Pingback: [Windows Phone 8.1] Reproducir Audio en Background - Javier Suárez

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s