Optimizando el arranque de nuestras apps en Windows Phone 8

Cuando hablamos de rendimiento de apps, decimos que una app tiene un buen rendimiento cuando arranca rápido, responde de manera inmediata a nuestras acciones, las animaciones son fluidas y suaves, el paso entre páginas es igualmente rápido,..., y además la app es eficiente y consume pocos recursos de memoria, datos de Internet y batería.

En este artículo he recopilado una serie de consejos para Windows Phone 8 alrededor del primer punto mencionado: la mejora del rendimiento en el arranque de las apps, desde que las lanzamos hasta que se muestra el contenido de su primera página. Ten en cuenta que la mayor parte de estos consejos aplica también a la apertura de páginas nuevas en nuestras apps. Yo en mi caso particular me tomo cada página nueva que se abre en mis apps como si fuese la primera de todas, e intento que desde que digo que se abra la página hasta que se muestre pase el menor tiempo posible.

Antes de empezar, no olvidemos que el rendimiento es cuestión de percepción. A veces no podremos hacer que las cosas pasen antes, pero sí que lo parezca.

Herramientas 

La mejor manera de probar nuestras apps para analizar su rendimiento es en un dispositivo real, en condiciones de conexión a Internet reales (p.ej. 3G sin demasiada buena cobertura), y con la app compilada y ejecutándose como lo haría al descargarse directamente de la Tienda (en su versión Release, con todas sus optimizaciones y sin un depurador adjuntado).

Si por alguna razón sólo podemos probar nuestras apps en el emulador de Windows Phone, podremos simular diversas condiciones de conexión a la red con el Simulation Dashboard.

Además, podemos lanzar nuestra app desde Visual Studio para que se ejecute como una Imagen Nativa Optimizada (Optimized Native Image) con Ctrl+F5 (Start without debugging) o Alt+F1 (Start Windows Phone Performance Analysis), como si la hubiéramos descargado de la Tienda. Puedes ver más información sobre este tema, incluido el cómo depurar la app optimizada, aquí:  How to test the retail version of your app.  

Y en cualquier caso, analiza el rendimiento de tu app con la herramienta  Windows Phone Application Analysis . Con esta herramienta podrás monitorizar tu app y hacer un completo profiling  de la misma .

La Splash Screen

La Splash Screen es esa imagen que aparece nada más arrancar muchas apps y que nos muestra un icono o imagen mientras que la app se carga e inicializa, y hasta que está lista para mostrar la primera página. Aunque puede tener sentido mostrar esta imagen (por ejemplo por motivos de marca, para mostrar el logo de la empresa), normalmente no debería de ser necesario para las apps de Windows Phone 8, ya que éstas pueden llegar a cargar bastante rápido.

Habrás visto que las propias apps del sistema no tienen Splash Screen, y en muchos casos el mero hecho de quitar esta imagen aumenta la percepción de velocidad en el arranque.

Si dejamos la Splash Screen y la app se pasa demasiado tiempo mostrándola, dará la sensación de que se ha colgado, y el usuario a buen seguro la matará. De hecho, es posible que ni pase la certificación de la Tienda. Y en cualquier caso, dará la sensación de tener un mal rendimiento. Por esa razón en muchas apps donde la inicialización se lleva demasiado tiempo se utiliza una Splash Screen extendida, que no es más que una primera página que se abre inmediatamente nada más cargar la app, que simula la Splash Screen y que muestra información de progreso al usuario mientras realiza toda la inicialización por debajo. Pero así éste puede por lo menos ver que sigue viva y haciendo cosas. Una vez terminada la inicialización se abre la página principal.

Por lo general da mucha más sensación de velocidad quitar la Splash Screen extendida y abrir la página principal lo antes posible, haciendo la inicialización y mostrando el progreso en la página principal directamente una vez mostrada. Al final, y si no cambiamos nada más, se va a tardar lo mismo en hacer la inicialización, pero el usuario estará dentro de la app lo antes posible, e incluso podrá empezar a interactuar con ella mientras la inicialización tiene lugar.

Assemblies

Cuanto más grande sea el binario (assembly) de la app, más tardará ésta en cargarse y en poder abrirse su primera página. La clave es minimizar por tanto su tamaño. Para ello deberíamos:

  • Poner el Build Action de todos los recursos que añadamos a nuestro proyecto (imágenes, audio, video, ficheros XML) como Content, y no como Embedded Resource. Así los recursos no estarán embebidos en los binarios de la app.
  • Utilizar Librerías de Clase de Windows Phone (Windows Phone Class Libraries) para almacenar por ejemplo páginas secundarias de la app a las que el usuario no vaya a acceder siempre que la arranque, como podría ser la página de configuración o el "acerca de". En caso de navegar a esas páginas, en lugar de usar una ruta como  "/Views/AboutInfoPage.xaml" usaríamos  "/MyLibrary;component/Views/AboutInfoPage.xaml" para localizar la página en la librería en la que está, que sólo se cargaría en ese momento. También podríamos poner lógica de negocio en esas librerías o en Librerías de Clase Portables (Portable Class Libraries o PCLs), que sólo se carguen cuando hagan falta.
  • Crear assemblies satélites para cada idioma soportado al localizar nuestra app.

Constructores y Eventos Loaded

En la medida de lo posible, tenemos que minimizar la cantidad de código que ejecutamos en el constructor de nuestra página y del manejador de su evento Loaded, ya que este código es ejecutado antes de que se muestre la página. De hecho, tanto los constructores de las páginas como de los controles analizan su XAML e instancian los objetos declarados en él. Así que si por ejemplo utilizamos el patrón MVVM y en el XAML de la página referenciamos el vista-modelo que le corresponde, el constructor de este vista-modelo también será ejecutado antes de poder mostrar la página. Por tanto tenemos que limitar también las operaciones que realizamos en estos métodos.

En general, si necesitamos cargar ficheros de nuestro Isolated Storage o hacer algún otro procesamiento intensivo, deberíamos de hacerlo más tarde (por ejemplo en el método OnNavigatedTo de la página, que se llama en cuanto la página se activa), o realizar las operaciones en hilos de ejecución secundarios (background threads) de manera que no bloqueemos el hilo de ejecución principal, el del interfaz de usuario, con estas operaciones.

Isolated Storage

Tenemos que ser conscientes del uso que hacemos del Isolated Storage de nuestra app. Si tenemos que lidiar con 1000 ficheros, podría ser lento. Así que debemos estructurar sabiamente el almacenamiento y usar subcarpetas, y como acabamos de comentar en el apartado anterior, considerar su procesamiento en hilos de ejecución secundarios.

Y si almacenamos la configuración de nuestra app en ApplicationSettings , también tendremos que tener cuidado si almacenamos ahí mucha información. La primera vez que se accede a un valor de este diccionario, se deserializa del Isolated Storage, y lleva tiempo. Así que hay que tener en cuenta cuándo accedemos a nuestra configuración, e intentar hacerlo en hilos de ejecución secundarios. Incluso es posible que pudiese interesarnos más serializar nosotros mismos la información de configuración a un fichero y no usar ApplicationSettings

Asincronía 

En Windows Phone 8, además de las APIs de .NET que utilizábamos en Windows Phone 7 para acceder a los diferentes servicios del sistema operativo, ahora podemos utilizar un nuevo conjunto de APIs llamado  Windows Phone Runtime . Estas APIs están formadas por un subconjunto de las APIs del Windows Runtime de Windows 8, y por otras APIs sólo disponibles en Windows Phone. Una de las principales características de estas nuevas APIs es que soportan programación asíncrona.

En el Windows Phone Runtime se han reemplazado muchos de los métodos antiguos con versiones asíncronas de los mismos. Si por ejemplo accedemos a un fichero con las APIs antiguas y estamos en el hilo de ejecución del interfaz de usuario, bloquearemos dicho hilo hasta que su ejecución termine, y la app no responderá a las acciones del usuario todo lo bien que debería. Si en su lugar accedemos al fichero con las nuevas APIs, éstas se ejecutarán de manera asíncrona y no bloquearán el hilo de ejecución del interfaz de usuario. El usuario podrá seguir interactuando con la aplicación y será feliz.

Así que no lo dudemos, y utilicemos las nuevas APIs siempre que podamos.

Árbol Visual y Data Bindings

Cuanto más complejo sea el XAML de nuestra página, más tardará ésta en cargarse. Por tanto es muy importante que identifiquemos posibles simplificaciones en nuestro árbol visual:

  • Evitemos anidar muchos paneles, grids, etc.
  • Utilicemos tamaños fijos para los elementos (p.ej. imágenes), y para las columnas y filas de los grids.
  • No utilicemos plantillas de datos (Data Templates) complejas.
  • Puede ser más rápido crear controles y cambiar propiedades en código que utilizar data bindings.
  • Es más rápido añadir propiedades especiales a nuestros objetos y utilizarlas en nuestros data bindings que utilizar Converters en el XAML para poder utilizar propiedades existentes.
  • Evitemos ineficiencias: colores de fondo (background) innecesarios, clips no rectangulares, máscaras de opacidad (opacity masks).
  • Tengamos cuidado con algunos objetos en XAML que pudieran tener una estructura interna compleja. Por poner un ejemplo sencillo, un TextBlock no tiene estructura interna, mientras que un TextBox está formado por un Grid, un Border y un ContentControl.

Si trabajamos con listas, tengamos en cuenta lo siguiente:

  • Es mejor utilizar el nuevo LongListSelector que un ListBox .
  • Aquí es especialmente importante que los Data Templates de los elementos de la lista sean lo más sencillos posible. Como acabamos de comentar, es mejor utilizar contenedores de tamaño fijo, evitar anidar estructuras, evitar Converters complejos y evitar controles complejos o personalizados (Custom Controls) dentro de cada elemento.
  • ¿Es necesario que carguemos todos los elementos de la lista de una vez? Porque podemos virtualizar la lista y cargar sus elementos bajo demanda. 

Si utilizamos el control Panorama  o el Pivot , tengamos en cuenta lo siguiente:

  • Pintar el control Panorama requiere mucha CPU.
  • El control Pivot es más rápido que el Panorama y consume menos memoria.
  • Por rendimiento y usabilidad, no deberíamos de tener más de 5 secciones en un Panorama. En el caso del Pivot, no es recomendable tener más de 4 (aunque en este caso está más relacionado con la usabilidad que con el rendimiento) . Deberíamos considerar mover el contenido de algunas secciones a nuevas páginas.
  • No es necesario que carguemos el contenido de todas sus secciones ( Panorama Item o PivotItem ) al principio. Podemos detectar cuándo cambiamos entre las secciones e ir cargando su contenido sólo cuando sea necesario.

Base de Datos Local

Una de las mejores prácticas que podemos aplicar si accedemos a datos de Internet es cachear esa información en local. La próxima vez que se lance la app podemos sacar la información de la caché y mostrársela al usuario inmediatamente, mientras que miramos en Internet si hay alguna actualización con la app ya iniciada. El arranque de la app será mucho más rápido y la sensación de velocidad mucho mayor.

En Windows Phone podemos almacenar datos relacionales en una base de datos local que reside en la carpeta local de la app. Si decidimos usarla para nuestra caché, lo siguiente mejorará el rendimiento desde el punto de vista de velocidad y consumo memoria:

  • Definir una columna de versión en nuestras entidades.
  • Implementar INotifyPropertyChanging en nuestras entidades.
  • Usar queries compiladas.

Aquí puedes ver más información sobre estas y otras mejores prácticas: Local database best practices for Windows Phone.  

Lanzamiento y Reactivación de la App

Cuando se lanza una nueva instancia de nuestra app, se ejecuta el método Application_Launching de la app antes de tratar de mostrar la primera página. Aquí, una vez más, deberíamos de minimizar el código a ejecutar para que la página se muestre lo antes posible. Si tenemos por ejemplo que cargar datos persistidos anteriormente, como podemos hacer en el método Application_Closing al cerrarse la app, ya lo haremos más tarde, a ser posible una vez cargada la página.

Si el usuario cambia a otra app sin cerrar la nuestra, la app se desactivará y en principio quedará suspendida en memoria (dormant state) con todo su estado intacto. Ahora, es posible que nuestra app suspendida pase a un estado llamado tombstoned, en cuyo caso la app no estará almacenada en memoria aunque podremos almacenar información de la app y sus páginas en unos diccionarios de estado (State dictionary). Cuando el usuario reactive nuestra app, se ejecutará el método Application_Activated de la app. En este método tendremos que determinar si venimos del dormant state o del tombstoned state, y para ello utilizaremos IsApplicationInstancePreserved que será True o False respectivamente . Si se ha preservado la app en memoria, no tendremos que hacer nada, y la reactivación debería de ser inmediata. Si venimos del tombstoned state tendremos que restaurar el estado guardado en los diccionarios.

Aquí puedes ver más sobre el ciclo de vida de las apps en Windows Phone: App activation and deactivation for Windows Phone.

Fast App Resume

Por defecto, una nueva instancia de nuestra app se lanza cada vez que el usuario arranca una nueva copia desde la lista de programas, o desde uno de sus live tiles secundarios, o desde una notificación, por ejemplo, aunque la app ya hubiese sido abierta antes y en ese momento estuviese suspendida. Esto hace que toda la carga e inicialización de la app tenga que hacerse de nuevo, lo que en algunas apps puede llevar mucho tiempo, sobre todo si lo comparamos con el tiempo mínimo que tarda una app suspendida en ser reactivada. Por suerte con Windows Phone 8 tenemos Fast App Resume , que reactiva una app que esté suspendida si el usuario lanza una nueva copia.

Activemos el Fast App Resume siempre que sea posible.

Uso de Patrones

Y para terminar, si desarrollas tus apps con un patrón como el MVVM, ten en cuenta lo siguiente sobre los patrones:

  • pueden hacer que los desarrolladores seamos más productivos.
  • nos guían.
  • normalmente NO hacen que la vida del usuario de nuestras apps sea mejor.
  • pueden crear capas de código que ralenticen la app.

Nota: que conste que yo siempre desarrollo con MVVM, y soy un enamorado del patrón. Pero si veo que algo es más eficiente hacerlo por ejemplo en el code behind de mi página, lo hago.

Conclusiones

  1. Consideremos cómo los usuarios perciben el rendimiento de nuestras apps.
  2. Entendamos qué podemos llegar a controlar nosotros.
  3. Usemos las herramientas y componentes adecuados.
  4. Simplifiquemos.
  5. Nunca hagamos ahora lo que podemos hacer después.

 

Puedes encontrar más consejos sobre rendimiento en Windows Phone aquí: App performance considerations for Windows Phone. También puedes ver mi webcast Optimiza tus apps de Windows Phone 8 y descargarte la presentación de dicho webcast.

Un saludo,

 

Alejandro Campos Magencio (@alejacma)

Microsoft Technical Evangelist

 

PD: Mantente informado de todas las novedades de Microsoft para los desarrolladores españoles a través del Twitter de MSDN, el Facebook de MSDN, el Blog de MSDN y la Newsletter MSDN Flash.