Tiempo de inicio de la app

Los usuarios esperan que las apps sean responsivas y se carguen con rapidez. Una app con inicio lento no cumple con esta expectativa y puede decepcionar a los usuarios. Este tipo de experiencia negativa puede hacer que un usuario califique tu app con una puntuación baja en Play Store o incluso que deje de usarla por completo.

En esta página, se proporciona información para ayudarte a optimizar el tiempo de inicio de tu app, lo que incluye una descripción general de las partes internas del proceso de inicio, la forma de generar perfiles del rendimiento del inicio y algunos problemas habituales relacionados con la hora de inicio, además de sugerencias para saber cómo abordarlos.

Información sobre los diferentes estados de inicio de la app

El inicio de una app se puede llevar a cabo en uno de estos tres estados: inicio en frío, inicio semicaliente o inicio en caliente. Cada estado determina el tiempo que tarda tu app en volverse visible para el usuario. En inicio en frío, tu app se inicia desde cero. En los otros estados, el sistema necesita llevar la ejecución de la app del segundo plano al primero.

Te recomendamos que siempre optimices tu app en función de la perspectiva de un inicio en frío, ya que esto también puede mejorar el rendimiento de los inicios semicalientes y en caliente.

Para optimizar tu app para un inicio rápido, es útil conocer qué sucede en los niveles del sistema y de la app, y cómo estos interactúan, en cada uno de los estados.

Dos métricas importantes para determinar el inicio de la app son el tiempo para la visualización inicial (TTID) y el tiempo para la visualización completa (TTFD). El TTID es el tiempo que lleva mostrar el primer fotograma, y el TTFD es el tiempo que tarda la app en ser totalmente interactiva. Ambos son igualmente importantes, ya que el TTID informa al usuario que la app se está cargando, y el TTFD muestra cuando la app ya se puede usar. Si alguno de estos es demasiado largo, es posible que el usuario salga de la app incluso antes de que se cargue por completo.

Para obtener valores precisos del TTFD, obtén un indicador cuando la app alcance un estado de visualización completa para garantizar que se midan los tiempos con exactitud. Si deseas obtener información para hacerlo, consulta Cómo mejorar la exactitud de los tiempos de inicio.

Inicio en frío

El inicio en frío hace referencia a una app que se inicia desde cero. Esto significa que, hasta este inicio, el proceso del sistema crea el proceso de la app. Los inicios en frío se producen, por ejemplo, cuando se inicia tu app por primera vez desde que se inició el dispositivo o desde que el sistema finalizó la app.

Este tipo de inicio presenta el mayor desafío a la hora de minimizar el tiempo de inicio, ya que el sistema y la app deben trabajar más que en los otros estados de inicio.

Cuando comienza el inicio en frío, el sistema tiene las siguientes tres tareas:

  1. Cargar e iniciar la app
  2. Mostrar una ventana de inicio en blanco para la app inmediatamente después del inicio
  3. Crear el proceso de la app

No bien el sistema crea el proceso de la app, este es responsable de las siguientes etapas:

  1. Crear el objeto de la app
  2. Iniciar el subproceso principal
  3. Crear la actividad principal
  4. Aumentar las vistas
  5. Diseñar la pantalla
  6. Realizar el dibujo inicial

Cuando el proceso de la app completa la apertura inicial, el proceso del sistema intercambia la ventana en segundo plano que se muestra y la reemplaza con la actividad principal. En este punto, el usuario puede comenzar a usar la app.

En la Figura 1, se muestra cómo los procesos del sistema y la app transfieren el trabajo entre sí.

Figura 1: Representación visual de las partes importantes del inicio en frío de una app.

Pueden surgir problemas de rendimiento durante la creación de la app y de la actividad.

Creación de la app

Cuando se inicia tu app, la ventana en blanco de inicio permanece en la pantalla hasta que el sistema termina de abrir la app por primera vez. En este punto, el proceso del sistema intercambia la ventana de inicio por tu app, lo que le permite al usuario interactuar con ella.

Si anulas Application.onCreate() en tu propia app, el sistema invoca el método onCreate() en el objeto de la app. Luego, la app genera el subproceso principal, también conocido como subproceso de IU, y le asigna la tarea de crear la actividad principal.

Desde este momento, los procesos del nivel del sistema y de la app continúan de acuerdo con las etapas del ciclo de vida de la app.

Creación de la actividad

Después de que el proceso de la app crea tu actividad, esta lleva a cabo las siguientes operaciones:

  1. Inicializa valores.
  2. Llama a constructores.
  3. Llama al método de devolución de llamada, como Activity.onCreate(), apropiado para el estado del ciclo de vida de ese momento de la actividad.

Por lo general, el método onCreate() produce el mayor impacto en el tiempo de carga, ya que realiza el mayor trabajo de sobrecarga: carga y aumenta vistas, e inicializa los objetos necesarios para que se ejecute la actividad.

Inicio semicaliente

El inicio semicaliente comprende un subconjunto de las operaciones que se llevan a cabo durante el inicio en frío. Al mismo tiempo, representa una sobrecarga mayor que en el caso del inicio en caliente. Muchos estados potenciales pueden ser considerados como inicios semicalientes, por ejemplo:

  • El usuario cancela la operación de tu app, pero luego vuelve a iniciarla. La ejecución del proceso podría continuar, pero la app debe recrear la actividad desde cero llamando a onCreate().

  • El sistema quita la app de la memoria y, luego, el usuario vuelve a iniciarla. Es necesario reiniciar el proceso y la actividad, pero de algún modo, la tarea puede aprovechar el paquete de estado de la instancia guardada, que se pasó a onCreate().

Inicio en caliente

Un inicio en caliente de la app tiene una sobrecarga menor que un inicio en frío. En un inicio en caliente, el sistema lleva tu actividad al primer plano. Si todas las actividades de la app están todavía en la memoria, la app puede evitar tener que repetir la inicialización del objeto, el aumento del diseño y la renderización.

Sin embargo, si se borró definitivamente una parte de la memoria en respuesta a eventos de recorte de memoria, como onTrimMemory(), será necesario recrear esos objetos como respuesta al evento de inicio en caliente.

El inicio en caliente muestra el mismo comportamiento en pantalla que el inicio en frío: el proceso del sistema muestra una pantalla en blanco hasta que la app termina de renderizar la actividad.

Figura 2: Un diagrama con los diferentes estados de inicio y sus respectivos procesos, cada uno de los cuales comienza desde el primer fotograma.

Cómo usar las métricas para detectar y diagnosticar problemas

Si quieres diagnosticar de manera adecuada el rendimiento del tiempo de inicio, puedes registrar métricas que muestren cuánto tarda tu app en iniciarse. Android ofrece varios medios para mostrarte que tu app tiene un problema y te ayuda a diagnosticarlo. Android vitals puede alertarte cuando se produce un problema, y las herramientas de diagnóstico pueden ayudarte a identificarlo.

Beneficios de usar métricas de inicio

Android usa las métricas de tiempo para la visualización inicial (TTID) y tiempo para la visualización completa (TTFD) para optimizar los inicios de apps semicalientes y en frío. Android Runtime (ART) usa los datos de estas métricas para precompilar
código de forma eficiente y optimizar los inicios en el futuro.

Los inicios más rápidos generan una interacción más prolongada de los usuarios con tu app, lo cual reduce las instancias en las que se sale de la app con antelación, se reinicia la instancia o se cambia a otra app.

Android vitals

Android vitals puede ayudarte a mejorar el rendimiento de tu app. Para ello, te envía alertas a través de Play Console cuando los tiempos de inicio de tu app son excesivos.

Android vitals considera excesivos los siguientes tiempos de inicio para tu app:

Android vitals usa la métrica de tiempo para la visualización inicial (TTID). Si deseas obtener información sobre cómo Google Play recopila datos de Android vitals, consulta la documentación de Play Console.

Tiempo para la visualización inicial

La métrica de tiempo para la visualización inicial (TTID) mide el tiempo que tarda una app en producir su primer fotograma, incluidas la inicialización del proceso durante un inicio en frío, la creación de la actividad durante un inicio en frío o semicaliente y la visualización del primer fotograma.

Cómo recuperar el TTID

Logcat incluye una línea de resultados que contiene un valor llamado Displayed. Este valor representa el tiempo transcurrido entre que se inició el proceso y se terminó de abrir la actividad correspondiente en la pantalla. El tiempo transcurrido comprende la siguiente secuencia de eventos:

  • Iniciar el proceso
  • Inicializar los objetos.
  • Crear e inicializar la actividad.
  • Aumentar el diseño
  • Dibujar tu app por primera vez

La línea de registro informada es similar al siguiente ejemplo:

ActivityManager: Displayed com.android.myexample/.StartupTiming: +3s534ms

Si realizas un seguimiento de resultados de logcat desde la línea de comandos o en un terminal, encontrarás el tiempo transcurrido de manera sencilla. Para encontrar el tiempo transcurrido en Android Studio, inhabilita los filtros en la vista de logcat. Esto es necesario porque el servidor del sistema, no la app misma, entrega este registro.

Después de realizar la configuración adecuada, puedes buscar el término correcto para ver la hora. En la Figura 3, se muestra cómo inhabilitar filtros seleccionando "No filters" en el menú desplegable y, en la segunda línea de resultados desde la parte inferior, se muestra un ejemplo de salida de logcat en relación con el tiempo Displayed.

Figura 3: Inhabilitación de filtros y búsqueda del valor Displayed en logcat.

La métrica Displayed de los resultados de logcat no necesariamente captura el tiempo transcurrido hasta que se cargan y muestran todos los recursos. Se omiten los recursos a los que no se hace referencia en el archivo de diseño o que la app crea como parte de la inicialización del objeto. Excluye estos recursos porque cargarlos es un proceso intercalado, y no se bloquea la pantalla inicial de la app.

En ocasiones, la línea Displayed de los resultados de logcat incluye un campo adicional para el tiempo total. Por ejemplo:

ActivityManager: Displayed com.android.myexample/.StartupTiming: +3s534ms (total +1m22s643ms)

En este caso, la primera medición de tiempo se realiza solo para la actividad que se inició primero. La medición de tiempo total comienza cuando se inicia el proceso de la app y puede incluir otra actividad que se haya iniciado primero, pero que no mostró nada en la pantalla. Solo se muestra la medición de tiempo total cuando hay una diferencia entre los tiempos de inicio de la actividad y el tiempo total de inicio.

También puedes ejecutar tu app con el comando ADB Shell Activity Manager para medir el TTID. Por ejemplo:

adb [-d|-e|-s <serialNumber>] shell am start -S -W
com.example.app/.MainActivity
-c android.intent.category.LAUNCHER
-a android.intent.action.MAIN

La métrica Displayed aparece en los resultados de logcat, como antes. En la ventana de terminal, se muestra lo siguiente:

Starting: Intent
Activity: com.example.app/.MainActivity
ThisTime: 2044
TotalTime: 2044
WaitTime: 2054
Complete

Los argumentos -c y -a son opcionales y te permiten especificar <category> y <action>.

Tiempo para la visualización completa

La métrica de tiempo para la visualización completa (TTFD) mide el tiempo que tarda la app en producir su primer fotograma con contenido completo, incluido el contenido que se carga de forma asíncrona después del primer fotograma. Por lo general, es el contenido principal de lista que se carga desde la red, según lo informa la app.

Cómo recuperar el TTFD

Puedes usar el método reportFullyDrawn() para medir el tiempo transcurrido entre el inicio de la app y la visualización completa de todos los recursos y las jerarquías de vistas, lo que puede ser útil en casos en los que una app realiza una carga diferida. En estos casos, la app no bloquea la apertura inicial de la ventana, pero carga recursos de manera asíncrona y actualiza la jerarquía de vistas.

Debido a la carga diferida, si la visualización inicial de una app no incluye todos los recursos, puedes considerar la carga y la visualización completas de todos los recursos y vistas como una métrica separada. Por ejemplo, tu IU podría estar completamente cargada con algo de texto, pero aún no mostrar imágenes que la app debe recuperar de la red.

Cómo mejorar la precisión del tiempo de inicio explica cómo usar FullyDrawnReporter para retrasar las llamadas a reportFullyDrawn hasta que tu app se vuelva interactiva para el usuario.

Cuando usas esta API, el valor que muestra logcat es el tiempo transcurrido entre la creación del objeto de la app y el momento en que se llama a reportFullyDrawn(). El siguiente es un ejemplo de resultado de logcat:

system_process I/ActivityManager: Fully drawn {package}/.MainActivity: +1s54ms

En ocasiones, el resultado de logcat incluye un tiempo total, como se describe en Tiempo para la visualización inicial.

Si detectas que los tiempos de visualización son más lentos de lo que esperas, puedes intentar identificar los cuellos de botella en el proceso de inicio.

Cómo identificar cuellos de botella

Para detectar cuellos de botella, puedes usar el Generador de perfiles de CPU de Android Studio. Para obtener más información, consulta Cómo inspeccionar la actividad de la CPU con el Generador de perfiles de CPU.

Además, puedes obtener información sobre los posibles cuellos de botella a través del seguimiento intercalado de los métodos onCreate() de tus apps y actividades. Para obtener más información sobre el seguimiento intercalado, consulta la documentación sobre las funciones de Trace y la descripción general del registro del sistema.

Cómo resolver problemas habituales

En esta sección, se analizan varios problemas que, a menudo, afectan el rendimiento de inicio de las apps. Por lo general, estos problemas están relacionados con la inicialización de apps y objetos de actividades, además de con la carga de pantallas.

Inicialización de app intensa

El rendimiento del inicio puede verse afectado cuando tu código anula el objeto Application y ejecuta trabajo pesado o lógica compleja cuando se inicializa ese objeto. Es posible que tu app pierda tiempo durante el inicio si las subclases de Application realizan inicializaciones que aún no son necesarias.

Algunas de estas pueden ser completamente innecesarias, como cuando se inicializa la información de estado para la actividad principal cuando la app se inicia en respuesta a un intent. Con un intent, la app usa solo un subconjunto de los datos de estado inicializados con anterioridad.

Otros desafíos que se presentan durante la inicialización de una app incluyen eventos de recolección de elementos no utilizados, que tienen un gran impacto o son numerosos, o E/S de disco que se producen junto con la inicialización y bloquean el proceso aún más. La recolección de elementos no utilizados se debe tener en cuenta específicamente para el tiempo de ejecución de Dalvik; el tiempo de ejecución de Android Runtime (ART) realiza la recolección de elementos no utilizados en simultáneo, lo que minimiza el impacto de esa operación.

Cómo diagnosticar el problema

Puedes usar el seguimiento de métodos o el registro en línea para diagnosticar el problema.

Seguimiento de métodos

Cuando se ejecuta el Generador de perfiles de CPU, se descubre que el método callApplicationOnCreate() llama finalmente a tu método com.example.customApplication.onCreate. Si la herramienta muestra que la ejecución de estos métodos tarda demasiado en completarse, sigue explorando para descubrir qué trabajo se está desarrollando.

Seguimiento intercalado

Usa el seguimiento intercalado para investigar posibles culpables, incluidos los siguientes:

  • La función onCreate() inicial de tu app
  • Cualquier objeto singleton que tu app inicializa
  • Cualquier E/S de disco, deserialización o bucle cerrado que pueda producirse durante el cuello de botella

Soluciones para el problema

Si el problema se relaciona con inicializaciones innecesarias o E/S de disco, la solución es la inicialización diferida. En otras palabras, solo inicializa los objetos que son inmediatamente necesarios. En lugar de crear objetos globales estáticos, cambia a un patrón singleton, en el que la app inicializa objetos solo la primera vez que los necesita.

Además, considera usar un framework de inyección de dependencias, como Hilt, que crea objetos y dependencias cuando se los inyecta por primera vez.

Si la app usa proveedores de contenido para inicializar los componentes de la app al inicio, considera usar la biblioteca de App Startup.

Inicialización de actividad intensa

A menudo, la creación de actividades implica mucho trabajo de sobrecarga alta. En general, hay oportunidades de optimizar este trabajo para obtener mejoras de rendimiento. Estos son algunos problemas habituales:

  • Ampliación de diseños grandes o complejos
  • Bloqueo de la pantalla cuando se escribe en el disco, o E/S de red.
  • Carga y decodificación de mapas de bits
  • Rasterización de objetos VectorDrawable
  • Inicialización de otros subsistemas de la actividad

Cómo diagnosticar el problema

También en este caso, tanto el registro de métodos como el registro en línea pueden ser útiles.

Seguimiento de métodos

Cuando uses el Generador de perfiles de CPU, presta atención a los constructores de la subclase Application y los métodos com.example.customApplication.onCreate() de tu app.

Si la herramienta muestra que la ejecución de estos métodos tarda demasiado en completarse, sigue explorando para descubrir qué trabajo se está desarrollando.

Seguimiento intercalado

Usa el seguimiento intercalado para investigar posibles culpables, incluidos los siguientes:

  • La función onCreate() inicial de tu app
  • Cualquier objeto singleton que tu app inicializa
  • Cualquier E/S de disco, deserialización o bucle cerrado que pueda producirse durante el cuello de botella

Soluciones para el problema

Si bien hay varios cuellos de botella posibles, los siguientes son dos problemas y soluciones habituales:

  • Cuanto más grande es tu jerarquía de vistas, más tarda la app en ampliarla. Puedes seguir estos dos pasos para abordar el problema:
    • Acopla la jerarquía de vistas reduciendo los diseños redundantes o anidados.
    • No amplíes partes de la IU que no necesiten ser visibles durante el inicio. En su lugar, usa un objeto ViewStub como marcador de posición para subjerarquías que la app pueda ampliar en un tiempo más apropiado.
  • Tener toda la inicialización de recursos en el subproceso principal también puede hacer que el inicio sea más lento. Puedes tratar este problema de la siguiente manera:
    • Transfiere toda la inicialización de recursos para que la app pueda llevarla a cabo de manera diferida en otro subproceso.
    • Permite que la app cargue y muestre las vistas, y luego actualiza las propiedades visuales que dependen de mapas de bits y otros recursos.

Pantallas de presentación personalizadas

Es posible que observes tiempo adicional durante el inicio si ya usaste uno de los siguientes métodos para implementar una pantalla de presentación personalizada en Android 11 (nivel de API 30) o versiones anteriores:

  • Usar el atributo del tema windowDisablePreview para desactivar la pantalla en blanco inicial que dibuja el sistema durante el lanzamiento
  • Usar una Activity dedicada

A partir de Android 12, se requiere migrar a la API de SplashScreen. Esta API permite un tiempo de inicio más rápido y te permite ajustar la pantalla de presentación de las siguientes maneras:

Además, la biblioteca de compatibilidad proporciona portabilidad a versiones anteriores de la API de SplashScreen para permitir la retrocompatibilidad y crear una apariencia coherente para la visualización de la pantalla de presentación en todas las versiones de Android.

Consulta la guía de migración de la pantalla de presentación para obtener más detalles.