jueves, 20 de marzo de 2014

Programación concurrente iOS


1.- Introducción

El iPhone 4 fue el primer dispositivo móvil de la marca Apple en incluir un procesador multi-core. Esta característica permite ejecutar varias tareas de forma concurrente de forma “real”. Esta ultima reseña es importante ya que en versiones anteriores al disponer de un solo núcleo la ejecución simultanea de tareas debía de “simularse” ().

Por tanto en la versión iOS 4 Apple se introdujeron entre otras cosas dos funcionalidad muy importantes al SDK : los bloques y el GCD (Gran Central Dispatch), para aprovechar al máximo esta nueva características en los dispositivos y facilitar la programación concurrente.


2.- Conceptos previos

En iOS todo nuestro programa se ejecutará sobre una misma hebra llamada hebra principal en la cual por “norma general” se debe ejecutar todo lo referente a la interfaz gráfica. Esta hebra además es la encargada de recibir los eventos generados por el usuario o por otras aplicaciones.

2.1.- Los bloques (Block):

Los bloques son fragmentos de código empaquetados y pueden ser visto como funciones en el sentido de que encapsulan un conjunto de sentencias pero tienen dos grandes diferencias con respecto a las funciones:

- Pueden ser pasados como parámetros a funciones o métodos.
- Los bloques capturan el entorno semántico en el que fueron definidos. Es decir tienen acceso a variables que están definidas fuera del bloque:

  • En el caso de las funciones las variables a las que podrá acceder serán a las declaradas de forma local y a las pasadas como parámetros.
  • En el caso de los bloques además de acceder a las variables locales y los parámetros, tambien puede acceder a las variables definidas en el mismo ámbito donde se definió el bloque. Ejemplo:
    int multiplicar = 10;
    int(^aumentar)(int) = ^ (int n){return n*multiplicador;};
    int valor = aumentar(3)  // valor = 30;
  Si se quisiera modificar el valor de la variable “multiplicar” se debería de declarar con el prefijo __block delante.

La forma de declarar un bloque (o un literal de bloque) es la siguiente:

^ tipoRetorno (parametros){
  cuerpo bloque
  return valor;
 }

Si quisiéramos almacenar este bloque en una variable para después poder usarlo se deberá almacenar en un puntero a bloque :
TipoRetorno (^nombrePunteroBloque) (parametros);

A continuación se muestra un ejemplo:

 int (^multiplica) (in, int) = ^ int (int a, int b){
  return a*b;
 }

 int c = multiplica(a*b);

Como vemos en el ejemplo a la izquierda de la asignación definimos el puntero a bloque (multiplica) y a la derecha de la asignación definimos el bloque con el código a ejecutar.


3.- Gran Central Dispatch

GCD es una api creada por Apple para el manejo y uso de procesos concurrentes de tal forma que podamos lanzar varias hebras de forma concurrente sin tener que preocuparnos de los detalles de más bajo nivel.

Haciendo uso de los bloques explicados en el apartado anterior para ejecutar varias tareas de forma paralela y fuera de la hebra principal basta con crear una cola en la cual se insertan dichas tareas. Estas tareas serán ejecutadas en diferentes hebras y estas hebras será gestionadas por la cola.

GCD nos proporciona diferentes colas para que se ajusten mejor a nuestras necesidades. En todas ellas domina o se gestionan por el orden FIFO (“primera tarea en entrar, primera en salir”):

- Serial dispatch queue (cola en serie): las tareas se ejecutan una detrás de otra, es decir solo se está ejecutando una tarea en un ciclo. Son las más usadas y son muy útiles cuando la ejecución de una tarea depende del resultado de otra o se quiere garantizar el acceso concurrente a un recurso.

- Concurrent dispatch queue (cola concurrente): por el contrario la cola concurrente ejecuta tantas tareas como pueda (dependiendo de estado del sistema) de forma concurrente sin necesidad de esperar a la finalización de otra. A partir de la versión iOS5 podemos crear nuestras propias colas concurrentes o usar las colas creadas por ellos o ya predefinidas que son: dispath_queue_priority_high DISPATH_QUEUE_PRIORITY_HIGH, DISPATH_QUEUE_PRIORITY_LOW, DISPATH_QUEUE_PRIORITY_DEFAULT y DISPATH_QUEUE_PRIORITY_BACKGROUND.

- Main queue (cola principal): se trata de una cola disponible de forma global y es la encargada de ejecutar las tareas asignadas a la hebra principal.

Después de toda esta explicación lo mejor será ver un sencillo ejemplo que ilustra muy bien todo lo explicado hasta el momento:

dispatch_queue_t colaEnSerie = dispatch_queue_create("miColaEnSerie", NULL);

dispatch_async(colaEnSerie, ^{
  NSLog(@"Entra en la tarea 1");
  [NSThread sleepForTimeInterval:3.0];
  NSLog(@"Sale de la tarea 1");
});

dispatch_async(colaEnSerie, ^{
  NSLog(@"Entra en la tarea 2");
  [NSThread sleepForTimeInterval:1.0];
  NSLog(@"Sale de la tarea 2");
});
En este ejemplo lo que hacemos es crear una cola en serie y asignarle dos tareas (cada tarea corresponde aun “bloque”) : una para que la hebra duerma durante 3s y la otra para que la hebra duerma 2s. El resultado obtenido es el siguiente:

2012-03-23 13:51:54.223 GDC Test[1587:12e03] Entra en la tarea 1
2012-03-23 13:51:57.228 GDC Test[1587:12e03] Sale de la tarea 1
2012-03-23 13:51:57.229 GDC Test[1587:12e03] Entra en la tarea 2
2012-03-23 13:51:58.231 GDC Test[1587:12e03] Sale de la tarea 2
Como se puede observar con claridad la tarea 2 no puede ejecutarse hasta que la tarea 1 no termina de ejecutarse y por tanto se muestra después aunque su duración sea menor debido al tipo de cola usado.

Ahora usaremos una cola concurrente:

dispatch_queue_t colaConcurrente = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

dispatch_async(colaConcurrente, ^{
  NSLog(@"Entra en la tarea 1");
  [NSThread sleepForTimeInterval:3.0];
  NSLog(@"Sale de la tarea 1");
});

dispatch_async(colaConcurrente, ^{
  NSLog(@"Entra en la tarea 2");
  [NSThread sleepForTimeInterval:1.0];
  NSLog(@"Sale de la tarea 2");
});
Y el resultado obtenido es el siguiente:

2012-03-23 13:54:48.497 GDC Test[1638:13003] Entra en la tarea 2
2012-03-23 13:54:48.497 GDC Test[1638:12f03] Entra en la tarea 1
2012-03-23 13:54:49.501 GDC Test[1638:13003] Sale de la tarea 2
2012-03-23 13:54:51.502 GDC Test[1638:12f03] Sale de la tarea 1
Ahora como se puede observar ambas tareas se a ejecutado al mismo tiempo terminando la tarea 2 antes debido a que su duración es menor.
Otro ejemplo un poco más complejo y algo más practico es el siguiente: en el cual se pretende mostrar por la pantalla del dispositivo una imagen descargada de internet. La forma tradicional implicaría que la descarga de la imagen se produjese en el ámbito de la hebra principal, siendo este proceso altamente costoso y por tanto bloqueando la hebra principal durante su ejecución. Esto tiene un gran error debido a que durante la ejecución del proceso de descarga la hebra principal permanecería bloqueada y por tanto cualquier evento generado por el usuario pasaría desapercibido. Esto al usuario le daría la sensación de que la aplicación no responde y se ha quedado “pillada” traduciéndose en un aumentando de forma considerable de su frustración. Por tanto para evitar esto lo que haremos será asignar el proceso de descarga a otra hebra liberando así a la hebra principal de esta tediosa tarea:

- (IBAction)segundoPlano:(id)sender {

// Creamos la URL para nuestra imagen
NSURL *url =
[NSURLURLWithString:@"http://www.kennethprimrose.co.uk/wp-content/uploads/2011/06/Scottish-landscape.jpg"];

// Obtenemos la cola de segundo plano
dispatch_queue_t backgroundQueue =

dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);

// Invocamos en modo asíncrono un bloque en la cola que hemos recuperado
dispatch_async(backgroundQueue, ^{

// Obtenemos los datos de la imagen (esto tardara un poco al
// tratarse de una imagen de alta resolución)
NSData *dataImagen = [NSData dataWithContentsOfURL:url];

// Creamos un UIImage con los datos obtenidos
UIImage *imagen = [[UIImage alloc] initWithData:dataImagen];

dispatch_async(dispatch_get_main_queue(), ^{

// Asignamos al UIImageView de nuestra vista dicho UIImage
self.imageView.image = imagen;

});

});

}
Si nos fijamos bien una vez descargada la imagen se delega la tarea de mostrarla por pantalla a la hebra principal introduciendo esta tarea en la cola primaria. Esto es debido a que no es seguro ejecutar el framework UIKIt (el encargado de todo lo relacionado con la IU) en segundo plano y se recomiendo que siempre se ejecuta bajo la hebra principal como se comento previamente.

Dado que en las colas lo que se encuentras son bloques de código y estos pueden contener variables “compartidas” ya sea porque son externas al bloque (mediante el prefijo __block delante de la variable se puede modificar dicha variable) o porque han sido pasadas como parámetros para su modificación, distintas tareas podrían modificar la misma variable al mismo tiempo y generar errores no deseados. Para evitar esto GCD nos proporciona, entre otras cosas, funciones para el uso de semáforos o acceso a Sección Crítica de forma sincronizada:

Semáforos: dispatch_semaphore
Acceso SC : dispatch_sync(my_queue, block)


4.- Conclusiones

En resumen como hemos podido observar la realización de nuestro programa de forma concurrente se vuelve algo muy sencillo con esta actualización que nos proporciona Apple, abstrayéndonos por completo de los problemas de bajo nivel que implica el trabajar con hebras al ser el sistema el encargado de la gestión de la cola de tareas. Además el esfuerzo por adaptar nuestra aplicación a una aplicación multi-thread se verá recompensado de cara al usuario proporcionándole una aplicación mucho más rápida y menos propensa a “cuelgues” no deseados.
 


5.- Bibliografía:




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

Procesos en Windows Phone (II): Launcher y Chooser




3.- Aplicaciones externas del sistema: Launcher y Chooser

Ya hemos visto como Windows Phone (WP en adelante) nos permite lanzar procesos externos con el fin de que el usuario realice tareas que preferimos delegar en otras aplicaciones. Además de esto, para algunas tareas concretas, WP nos da una serie de elementos para lanzar aplicaciones por defecto del sistema: los Launcher y los Chooser. Entendemos por aplicaciones del sistema las que forman parte de WP por defecto, dejando de lado las aplicaciones de terceros como la que desarrollariamos nosotros. Desgraciadamente esto también significa que no es posible implementar nuestros propios Launcher y Chooser.

Los Launcher y Chooser son limitados y los proporciona WP. Aquí teneis las listas completas con tutoriales de como usarlos:

La principal diferencia entre Launcher y Chooser es que el primero solo lanza la aplicación mientras que el segundo además devuelve algún tipo de resultado a nuestra aplicación cuando la tarea finaliza. Para entender mejor como se utilizan veremos a continuación un par de ejemplos a partir del código.


3.1.- Ejemplo de uso: Launcher:

Para ver un ejemplo práctico del uso de un Launcher he escogido el de lanzar el reproductor multimedia para mostrar un video al usuario. Basta con añadir los siguientes trozos de código al código de nuestra página (MyPage.xaml.cs por ejemplo).

Empezamos importando las bibliotecas correspondientes:
using System;
using Microsoft.Phone.Tasks;

El siguiente trozo de código es el que finalmente lanza la aplicación. Podeis añadirlo como el código del callback que se ejecutará al pulsar un botón por ejemplo:

//Inicializamos el Launcher
MediaPlayerLauncher mediaPlayerLauncher = new MediaPlayerLauncher();

// Indicamos el archivo de video a reproducir
mediaPlayerLauncher.Media = new Uri("MyVideo.wmv", UriKind.Relative);
// Indicamos donde se encuentra
mediaPlayerLauncher.Location = MediaLocationType.Data;
// Indicamos los controles disponibles para el usuario
mediaPlayerLauncher.Controls = MediaPlaybackControls.Pause | MediaPlaybackControls.Stop;
// Indicamos la orientación de la pantalla
mediaPlayerLauncher.Orientation = MediaPlayerOrientation.Landscape;

//Lanzamos el reproductor
mediaPlayerLauncher.Show();
Es así de sencillo. El sistema se encargará de llamar a las funciones apropiadas para salvar el estado de vuestra aplicación.


3.2.- Ejemplo de uso: Chooser:

En este caso veremos un ejemplo de Chooser con el que el usuario escojerá un contacto de su lista de contactos.

El proceso de inicializar y lanzar un Chooser es muy similar al de un Launcher, salvo que en este caso tenemos que indicar la parte de nuestro código que se encargará de manejar el resultado del Chooser.

Empezamos importando las bibliotecas correspondientes:
using Microsoft.Phone.Tasks;

Inicializamos y lanzamos el Chooser:
PhoneNumberChooserTask phoneNumberChooserTask;
phoneNumberChooserTask = new PhoneNumberChooserTask();
// Añadimos el callback
phoneNumberChooserTask.Completed += new EventHandler<PhoneNumberResult>(phoneNumberChooserTask_Completed);
phoneNumberChooserTask.Show();
La penúltima línea es la que marca la diferencia. En dicha línea le estamos añadiendo a nuestro Chooser una función a modo de callback que se ejecutará cuando la nueva aplicación termine.

Este sería el código de ejemplo de nuestro callback, que añadiriamos al código de nuestra página:
void phoneNumberChooserTask_Completed(object sender, PhoneNumberResult e)
{
    // Si el resultado es positivo, es decir, si el usuario ha escogido un contacto...
    if (e.TaskResult == TaskResult.OK)
    {
        MessageBox.Show("The phone number for " + e.DisplayName + " is " + e.PhoneNumber);
    }
}

En este caso el resultado se encapsula en un objeto PhoneNumberResult que está formado por un nombre (DisplayName) y un número de teléfono (PhoneNumber). Nuestra función simplemente lo mostrará por pantalla.

Con esto ya tendríamos integrado funcionalidad básica de nuestro teléfono. Es una buena alternativa al uso de protocolos y URIs aunque la lista de posibilidades sea limitada.

Procesos en Windows Phone (I): Introducción y asociación de URIs


1.- Introducción

Al igual que en otros SO móviles como Android, en Windows Phone (WP en adelante) podemos lanzar otros procesos desde nuestra aplicación ya sea para que se ejecuten en foreground (primer plano) o en background (segundo plano).

Los procesos en foreground cubiertos a continuación corresponden a aplicaciones externas a la nuestra, no a la navegación entre páginas dentro de nuestra aplicación. Si ese es el tema que te interesa haces mejor en seguir el siguiente enlace para aprender más sobre la navegación en WP: Navegación en Windows Phone.

Antes de continuar aclararé que no se cubren los aspectos sobre como crear la aplicación en Visual Studio o como lanzarla en un emulador para centrarnos más en la parte del lanzamiento y manejo de procesos. Para aprender sobre esto: Primeros pasos.

Por ultimo aclarar que para no duplicar código y no perder legibilidad los ejemplos de código están unicamente en C#, no hay código en VisualBasic.


2.- Aplicaciones externas: Asociación de URIs

Windows Phone nos proporciona funcionalidad para asociar nuestras aplicaciones a cierto tipo de archivos (para que al abrirlos se lance nuestra aplicación) y para que sean visibles al resto de aplicaciones, que podrán lanzarlas en cualquier momento. Esto se consigue a través de la construcción y registro de una serie de Uniform Resource Identifier (URI en adelante) que identificarán los tipos de archivo y protocolos de comunicación app-to-app que maneja.

A partir de ahora nos centraremos en el segundo caso: protocolos de comunicación app-to-app, aunque la mecánica del primero no es muy distinta.

Empecemos con un ejemplo teórico: imaginemos que tenemos una aplicación que gestiona podcasts (creacion, borrado, actualizaciones...). Sería interesante que cualquier reproductor de audio puediese utilizar nuestra aplicación para no tener que reimplementar toda la funcionalidad especifica de los podcast y se centrase simplemente en reproducir los archivos descargados.

Para ello nuestra aplicación se asociará a un esquema URI y las demás aplicaciones solo tienen que navegar a una URI con ese esquema para lanzar nuestra aplicación. Un ejemplo mas concreto del esquema URI sería:

podcasts:ShowPodcast?ID=42e-aff4
podcasts:ShowAllPodcasts?UpdateEnabled=true&OrderBy=date
podcasts:DisablePodcastUpdate?ID=78dl-gvw42

En los esquemas usados en WP debemos diferenciar dos partes principales que son las que están separadas por los dos puntos (:). La primera es el nombre del esquema, lo que identifica el protocolo utilizado y lo que usa WP para encontrar las aplicaciones que son capaces de responder a esa URI (podcasts en este caso). La segunda parte es totalmente libre, podéis poner lo que queráis. Podemos utilizarla para identificar operaciones dentro del protocolo (ShowPodcast, DisablePodcastUpdate...) y darle argumentos (ID, OrderBy...) como en nuestro ejemplo. Solo debéis tener en cuenta que estas URI serán usadas por otras aplicaciones, lo que significa que otros desarrolladores deberán saber como usarlas sin problemas, asi que tomaros el tiempo necesario para diseñar un buen protocolo.

Antes de seguir a un ejemplo práctico hay que decir que implementar un protocolo que solo utilizará vuestra aplicación no sirve de mucho a no ser que otras aplicaciones quieran usar expresamente tu aplicación. Lo mejor es implementar protocolos ya existentes. Si vamos a desarrollar una aplicación para podcasts, deberíamos usar el/los protocolo/s que usen otras aplicaciones de este tipo.

Cuando hay varias aplicaciones que responden al mismo protocolo y se intenta navegar a una URI perteneciente a este, el sistema le preguntará al usuario que aplicación le gustaría utilizar. Una aplicación que quiera manejar podcasts utilizará el lenguaje (protocolo) más común para esto, lanzando un mensaje dirigido "a quien le pueda interesar". Adaptarnos a ese protocolo en lugar de tener solo uno propio (que no conoce nadie) nos permite competir con otras aplicaciones con funcionalidad similar. Si os interesa aquí podréis ver una lista de protocolos ya existentes: Lista de protocolos. También hay que decir que existe una lista de protocolos reservados por el sistema. Cualquier llamada a uno estos protocolos será atendida por el sistema, ignorando nuestra aplicación por completo.


2.1.-Ejemplo de uso

Vamos a seguir con nuestro ejemplo de los podcasts para este ejemplo de uso. En resumen deberemos seguir una serie de pasos:
  • Registrar el protocolo
  • Implementar un URIMapper
  • Registrar nuestro URIMapper en el frame de nuestra aplicación.

Registrar el protocolo. Esto debemos hacerlo en el WMAppManifest.xml de nuestra aplicación. En el apartado de extensiones (Extensions) añadiremos los protocolos a los que estamos registrados (hasta un máximo de 10):
<Extensions>
  <Protocol Name="podcasts" NavUriFragment="encodedLaunchUri=%s" TaskID="_default" />
</Extensions>

En Name pondríamos el nombre de nuestro protocolo. Los dos atributos restantes deben tener siempre esos valores.

Implementar un URIMapper. Cuando una aplicación navega a una URI como las del apartado anterior, el sistema la codifica usando percent-encoding y se la envía al objeto URIMapper de nuestra aplicación. El objetivo de este URIMapper es traducir esa URI en la URI que identifica la página de la aplicación que responderá a la llamada junto con sus argumentos. Si no la puede traducir devolverá la URI original para que el sistema lance nuestra aplicación de forma normal (como si el usuario la hubiese lanzado desde su menú de aplicaciones).

Por ejemplo veamos las URIs anteriores codificadas:
/Protocol?encodedLaunchUri=podcasts%3AShowPodcast%3FID%3D42e-aff4
/Protocol?encodedLaunchUri=podcasts%3AShowAllPodcasts%3FUpdateEnabled%3Dtrue%26OrderBy%3Ddate
/Protocol?encodedLaunchUri=podcasts%3ADisablePodcastUpdate%3FID%3D78dl-gvw42

Y aquí una posible traducción al dominio de URIs de nuestra aplicación:
/ShowPodcastDetail.xaml?id=42e-aff4
/ShowListPodcasts.xaml?upd_enabled=true&order_by=date
/EditPodcastUpdate.xaml?id=78dl-gvw42&op=disable

Donde la primera parte (hasta el '?') identifica la página y el resto son los argumentos.

Pasemos a la implementación. Creamos un archivo MyUriMapper.cs donde añadiremos el siguiente código:
using System;
using System.Windows.Navigation;

namespace sdkAutoLaunch
{
  class MyUriMapper : UriMapperBase
  {
    private string tempUri;

    public override Uri MapUri(Uri uri){
      // Descodificamos la URI
      tempUri = System.Net.HttpUtility.UrlDecode(uri.ToString());

      // Identificamos la operación
      if (tempUri.Contains("podcasts:ShowPodcast?ID=")){
        // Obtnemos el valor de los argumentos
        int idIndex = tempUri.IndexOf("ID=") + 3;
        string id = tempUri.Substring(categoryIdIndex);

        // Devolvemos la URI de la página a utilizar
        return new Uri("/ShowPodcastDetail.xaml?id=" + id, UriKind.Relative);
      }
      
      // Hacemos lo mismo con el resto de operaciones
      if (tempUri.Contains("podcasts:ShowAllPodcasts?")){
        // ...
      }

      // ...

      // Si no reconocemos la URI la devolvemos
      return uri;
    }
  }
}

Registrar nuestro URIMapper en el frame de nuestra aplicación. Para esto editaremos el método InitializePhoneApplication del archivo App.xaml.cs. Añadiremos la siguiente línea justo después de que se haya inicializado RootFrame.Navigated:
RootFrame.UriMapper = new MyUriMapper();

Un ejemplo de InitializePhoneApplication resultante:
private void InitializePhoneApplication(){
  if (phoneApplicationInitialized)
    return;

  RootFrame = new PhoneApplicationFrame();

  RootFrame.Navigated += CompleteInitializePhoneApplication;

  // Aqui asignamos nuestro URIMapper

  RootFrame.UriMapper = new MyUriMapper();
   RootFrame.NavigationFailed += RootFrame_NavigationFailed;
  phoneApplicationInitialized = true;
}

Ya sabemos como hacer que nuestra aplicación sea capaz de lanzarse según una URI asociada. Solo nos queda saber como otras aplicaciones lanzarían la nuestra (o como nosotros podemos lanzar otras aplicaciones):
private async void LaunchPodcastsViewer(long id){
  Windows.System.Launcher.LaunchUriAsync(new System.Uri("podcasts:NewProducts?ID="+id.ToString()));
}

Con esto solo necesitaríamos encontrar el protocolo adecuado, tanto si estamos desarrollando una aplicación que espera ser llamada como si queremos hacer uso de otras aplicaciones.