viernes, 14 de marzo de 2014

Procesos en Windows Phone (III): Procesos en segundo plano


4.- Procesos en segundo plano

En ocasiones querremos que nuestra aplicación realice ciertas tareas a pesar de no estar siendo usada por el usuario en ese momento, como actualizar contenido a partir del obtenido de un servicio web. Para ello Windows Phone (WP en adelante) nos proporciona los ScheduledTask, tareas que se ejecutarán en background.

Existen dos tipos de ScheduledTask a nuestra disposicion: PeriodicTask y ResourceIntensiveTask. El primero es una pequeña tarea que se ejecuta periodicamente mientras que el segundo es una tarea de mayor peso, que consume una gran cantidad de recursos del sistema y que se ejecuta una sola vez y por más tiempo. Aunque nuestra aplicación solo puede registrar una tarea en segundo plano, puede registrarla para que sea de los dos tipos. Ambos tipos comparten una serie de características o restricciones impuestas por el sistema. De este modo WP se asegura de que ningun ScheduledTask entorpezca el uso del dispositivo por parte del usuario o el desempeño de otros ScheduledTasks:
  • API limitada. El conjunto de APIs del sistema a la que puede acceder un ScheduledTask es menor comparado con el que puede acceder una aplicación. Siga el siguiente enlace para ver exactamente a que partes puede o no acceder un ScheduledTask: API disponible.
  • Memoria limitada. La cantidad de memoria a nuestra disposición se recorta para que nuestro ScheduledTask no consuma tanta memoria que otras tareas (en primer o segundo plano) no tengan suficiente memoria como para ejecutarse correctamente. En dispositivos con una memoria de 1GB o mayor, el limite es de 25MB. En dispositivos con menos de 1GB el límite es de 11MB.
  • Replanificación cada dos semanas. Aunque podemos especificar el tiempo que nuestra tarea estará planificada para su ejecución, este tiempo nunca puede exceder dos semanas. Esto significa que para que nuestra tarea pueda estar planificada más tiempo nuestra aplicación debe añadirla a la planificación periodicamente.
  • Eliminadas de la planificación tras dos fallos. Si nuestra tarea falla (debido a una excepción o a exceder el límite de memoria) dos veces consecutivas el sistema la eliminará de la planificación. Nuestra aplicación deberá añadirla de nuevo para que pueda volver a ejecutarse.

Además de estas características comunes cada tipo de ScheduledTask tiene sus propias restricciones. Algunas pueden resultar demasiado limitadoras, pero su objetivo es el de un mejor desempeño global del sistema.

En el caso de ResourceIntensiveTask podemos ver que las restricciones nos indican que este tipo está destinado para tareas de gran concumo como la de actualización del sistema:
  • Duración máxima de 10 minutos.
  • El dispositivo debe estar conectado a una fuente de alimentación
  • El dispositivo no puede estar conectado a a red a través de un servicio telefónico de datos.
  • La capacidad de la batería del dispositivo debe estar como mínimo al 90%.
  • El dispositivo debe estar en modo pantalla bloqueada.
  • No puede ejecutarse si se está llevando a cabo una llamada telefónica

Para el caso de PeriodicTask:
  • Se ejecutarán en intervalos de 30 minutos.
  • Duración máxima aproximada de 25 segundos.
  • El modo de "ahorro de energía" puede impedir que nuestra tarea se ejecute.
  • Cantidad de tareas planificadas limitada. Este límite puede cambiarse por el usuario pero debe ser como mínimo de seis.

Algunas de estas restricciones pueden esquivarse en las versiones de desarrollo de nuestra aplicación, para así poder probar y depurar nuestras ScheduledTask sin problemas.


4.1.- Ejemplo de uso: ScheduledTask (PeriodicTask)

Aunque el siguiente ejemplo será el de un PeriodicTask, el uso de un ResourceIntensiveTask es idéntico. Lo único que cambia es como el sistema gestionará y ejecutará nuestro ScheduledTask, siguiendo las restricciones antes explicadas.

Crearemos primero la App clickando en el menú File -> Add -> New Project... Ahora seleccionamos al final del todo Windows Phone Scheduled Task Agent, le damos un nombre (MySchedTask por ejemplo) y confirmamos. Se habrá creado automaticamente un proyecto llamado MySchedTask y dentro de él un archivo ScheduledAgent.cs. Dentro de este archivo veremos una clase llamada ScheduledAgent, con un método llamdo OnInvoke(ScheduledTask task), donde añadiremos el código de nuestra tarea:
protected override void OnInvoke(ScheduledTask task)
{
  //Podemos registrar nuestro task para que sea de los dos tipos distintos y comprobar aqui con que tipo
  //concreto ha sido lanzado.
  if (task is PeriodicTask){
    // ...
  }else{
    // ...
  }

  // ...

  if (exito){
    NotifyComplete();
  }else{
    Abort();
  }
}

El método OnInvoke debe llamar al final a una de las funciones que vemos en el último if-else. Si ha tenido exito y ha completado su tarea en esta ejecución llamará a NotifyComplete(). En caso contrario (algo le ha impedido llevar a cabo su tarea) a Abort().

A continuación editamos el WMAppManifest.xml para incluir nuestra nueva tarea y que sea reconocida por el sistema:
<Tasks>
  <!-- Esta es la tarea de la página principal de nuestra aplicación -->
  <DefaultTask Name="_default" NavigationPage="MainPage.xaml"/>

  <!-- Esto es lo que debemos añadir para incluir nuestra nueva tarea -->
  <ExtendedTask Name="BackgroundTask"> 
    <BackgroundServiceAgent Specifier="ScheduledTaskAgent"
 Name="MySchedTask"
 Source="MySchedTask"
 Type="MySchedTask.ScheduledAgent"/>
  </ExtendedTask>
</Tasks>

Ahora ya solo queda modificar nuestra aplicación para que lance nuestro ScheduledTask. En el SolutionExplorer de Visual Studio hacemos click en el proyecto de nuestra aplicación y a continuación en el menú Project -> Add Reference... Seleccionamos la pestaña Projects y elegimos el proyecto de nuestro ScheduledTask (MySchedTask).

En el código de nuestra aplicación crearemos una función llamada StartTask() que añadirá el ScheduledTask a la planificación de tareas del sistema. Hay que tener en cuenta que si la tarea ya se encuentra ahí no hay manera de actualizarla. Deberemos eliminarla y añadirla de nuevo.
private void StartTask()
{
  String periodicTaskName = "PeriodicAgent"

  // Si la tarea ya se encuentra en ScheduledActionService la eliminamos
  periodicTask = ScheduledActionService.Find(periodicTaskName) as PeriodicTask;
  if (periodicTask != null){
    try{
      ScheduledActionService.Remove(periodicTaskName);
    }catch (Exception){
    }
  }

  //Creamos la tarea
  periodicTask = new PeriodicTask(periodicTaskName);

  // Le añadimos una descripción que será la que el usuario vea en la pantalla de procesos en segundo plano
  // en Preferencias
  periodicTask.Description = "Esta es mi pequeña tarea. En el fondo no hace nada :(";

  try{
    // Añadimos nuestra tarea a la planificación
    ScheduledActionService.Add(periodicTask);
    PeriodicStackPanel.DataContext = periodicTask;

  }catch (InvalidOperationException exception){
    // En caso de que el usuario tenga desactivada la opción de procesos en segundo plano...
    if (exception.Message.Contains("BNS Error: The action is disabled")){
      MessageBox.Show("El usuario no me deja ejecutarme. El se lo pierde...");
      // ...
    }
  
    // En caso de que el límite de procesos en segundo plano haya sido excedido...
    if (exception.Message.Contains("BNS Error: The maximum number of ScheduledActions of this type have already been added.")){
      // ...
    }
  }catch (SchedulerServiceException){
    // ...
  }
}

Con esto ya tendremos una tarea planificable y ejecutable.

Por último veremos como podemos lanzar la tarea siempre que queramos, esquivando como dije antes algunas de las restricciones:
ScheduledActionService.LaunchForTest(task.Name, TimeSpan.FromSeconds(60));

El primer argumento es el nombre de la tarea y el segundo el tiempo que queremos que se espera antes de lanzarla. Podemos usar esta función en nuestra aplicación para lanzar la tarea sin depender del planificador del sistema. También podríamos usarla dentro del código de la propia tarea para tener un intervalo de ejecución determinado por nosotros. Hay que tener en cuenta que esta función solo debe usarse durante el desarrollo para probar y depurar las tareas. En ningún caso debe haber una llamada a esta función en el código de la aplicación cuando la distribuyamos.


5.- Conclusiones 

Hemos visto el lanzamiento de procesos de distintos tipos. Aunque aún falta profundizar más en ciertos detalles la línea general es que en WP es insultantemente fácil usarlos. Quizá la asociación de URIs pueda parecer más engorrosa, pero en la práctica y usando alguna biblioteca de soporte (para parsear) creo que se convierte en un método muy sencillo e intuitivo de integrar otras aplicaciones. La única nota negativa quizá la dan los procesos en segundo plano, que imponen una serie de restricciones demasiado desalentadoras.

Referencias

No hay comentarios:

Publicar un comentario