Análisis y optimización del inicio de una app

Durante el inicio, tu app genera la primera impresión en los usuarios. El inicio de la app debe ser rápido y mostrar la información que el usuario necesita para utilizarla. Si tu app tarda demasiado en iniciarse, es posible que los usuarios la abandonen porque deben esperar demasiado tiempo.

Recomendamos usar la biblioteca de Macrobenchmark para hacer una medición del inicio, ya que proporciona una descripción general y registros detallados del sistema para ver exactamente lo que sucede durante el inicio.

Los registros del sistema proporcionan información útil sobre lo que sucede en el dispositivo. Estos datos te permiten comprender qué hace la app durante el inicio y te ayudan a identificar las áreas que podrías optimizar.

Para analizar el inicio de tu app, haz lo siguiente:

Pasos de análisis y optimización del inicio

A menudo, las apps necesitan cargar recursos específicos críticos para los usuarios finales durante el inicio. Los recursos que no son esenciales pueden esperar a que se carguen hasta que se complete el inicio.

Para compensar el rendimiento, considera lo siguiente:

  • Usa la biblioteca de Macrobenchmark para medir el tiempo que tarda cada operación y, luego, identifica los bloques que tardan mucho en completarse.

  • Confirma que la operación de uso intensivo de recursos sea fundamental para el inicio de la app. Si la operación puede esperar hasta que la app se cargue por completo, esto puede ayudar a minimizar las restricciones de recursos en el inicio.

  • Asegúrate de que esta operación se ejecute al inicio de la app. A menudo, se puede llamar a operaciones innecesarias desde código heredado o bibliotecas de terceros.

  • Si es posible, mueve las operaciones de larga duración a segundo plano. Los procesos en segundo plano aún pueden afectar el uso de CPU durante el inicio.

Después de investigar por completo la operación, puedes decidir la compensación entre el tiempo que tarda en cargarse y la necesidad de incluirla en el inicio de la app. Recuerda incluir la posibilidad de regresión o cambios rotundos cuando modifiques el flujo de trabajo de tu app.

Optimiza y vuelve a medir el tiempo de inicio de tu app hasta que estés conforme. Para obtener más información, consulta Cómo usar las métricas para detectar y diagnosticar problemas.

Mide y analiza el tiempo empleado en las operaciones principales

Una vez que tengas un seguimiento completo del inicio de la app, observa el seguimiento y mide el tiempo que se emplea para realizar las operaciones principales, como bindApplication o activityStart. Te recomendamos que uses Perfetto o el generador de perfiles de Android Studio para analizar estos seguimientos.

Observa el tiempo total que se dedicó al inicio de la app para identificar las operaciones que hacen lo siguiente:

  • Ocupan mucho tiempo y se pueden optimizar. Cada milisegundo es importante para el rendimiento. Por ejemplo, busca tiempos de dibujo de Choreographer, tiempos de aumento de diseño, tiempos de carga de la biblioteca, transacciones Binder o los tiempos de carga de los recursos. Para un inicio general, puedes ver todas las operaciones que tardan más de 20 ms.
  • Bloquean el subproceso principal. Para obtener más información, consulta Cómo navegar por un informe de Systrace
  • No es necesario que se ejecuten durante el inicio.
  • Podrían esperar hasta después de que se dibuje el primer fotograma.

Investiga más cada uno de estos seguimientos para encontrar brechas en el rendimiento.

Identifica las operaciones costosas en el subproceso principal

Se recomienda mantener las operaciones costosas, como la E/S de archivos y el acceso a la red, fuera del subproceso principal. Esto es igualmente importante durante el inicio de la app, ya que las operaciones costosas en el subproceso principal pueden hacer que la app no responda y retrasar otras operaciones críticas. StrictMode.ThreadPolicy puede ayudar a identificar casos en los que se ejecutan operaciones costosas en el subproceso principal. Se recomienda habilitar StrictMode en las compilaciones de depuración para identificar problemas lo antes posible, como se muestra en el siguiente ejemplo:

Kotlin

class MyApplication : Application() {

    override fun onCreate() {
        super.onCreate()

        ...
        if (BuildConfig.DEBUG)
            StrictMode.setThreadPolicy(
                StrictMode.ThreadPolicy.Builder()
                    .detectAll()
                    .penaltyDeath()
                    .build()
            )
        ...
    }
}

Java

public class MyApplication extends Application {

    @Override
    public void onCreate() {
        super.onCreate();

        ...
        if(BuildConfig.DEBUG) {
            StrictMode.setThreadPolicy(
                    new StrictMode.ThreadPolicy.Builder()
                            .detectAll()
                            .penaltyDeath()
                            .build()
            );
        }
        ...
    }
}

El uso de StrictMode.ThreadPolicy habilita la política de subprocesos en todas las compilaciones de depuración y hace que la app falle cada vez que se detectan incumplimientos de la política de subprocesos, lo que hace que sea difícil pasar por alto incumplimientos de política de subprocesos.

TTID y TTFD

Para ver el tiempo que tarda la app en producir su primer fotograma, mide el tiempo hasta la visualización inicial (TTID). Sin embargo, ten en cuenta que esta métrica no refleja necesariamente el tiempo que transcurre hasta que el usuario puede comenzar a interactuar con tu app. La métrica de tiempo para la visualización completa (TTFD) es más útil para la medición y optimización de las instrucciones de código necesarias para tener un estado de app que se pueda usar por completo.

Si quieres ver estrategias para informar cuando la IU de la app se cargó por completo, consulta Mejora la precisión de los tiempos de inicio.

Realiza optimizaciones para TTID y TTFD, ya que ambos son importantes en sus propias áreas. Un TTID bajo ayuda al usuario a ver que se está iniciando la app. Es importante que el TTFD sea corto para garantizar que el usuario pueda comenzar a interactuar con la app rápidamente.

Analiza el estado general de los subprocesos

Selecciona el tiempo de inicio de la app y observa segmentos de subprocesos generales. El subproceso principal debe ser responsivo en todo momento.

Las herramientas como el Generador de perfiles de Android Studio y Perfetto proporcionan una descripción general detallada del subproceso principal y cuánto tiempo se dedica a cada etapa. Si deseas obtener más información para visualizar los seguimientos de Perfetto, consulta la documentación de IU de Perfetto.

Identifica los períodos más largos de estado de suspensión del subproceso principal

Si la app pasa demasiado tiempo suspendida, es probable que se deba a que su subproceso principal esté esperando que se completen acciones. Si tienes una app con varios subprocesos, identifica cuál es el que bloquea el proceso principal y procura optimizar esas operaciones. También puede ser útil asegurarte de que no haya una contención de bloqueo innecesaria que cause retrasos en las instrucciones críticas.

Reduce los bloqueos del subproceso principal y la suspensión sin interrupciones

Busca cada instancia del subproceso principal que entre en estado de bloqueo. Perfetto y el generador de perfiles de Studio muestran esto con un indicador naranja en el cronograma del estado del subproceso. Identifica las operaciones, explora si son esperables o se pueden evitar, y haz una optimización cuando sea necesario.

La suspensión con interrupciones relacionada con el IO puede ser una muy buena oportunidad de mejora. Otros procesos que realizan operaciones de IO, incluso si no son apps relacionadas, pueden competir con el IO que realiza la app principal.

Mejora el tiempo de inicio

Después de identificar una oportunidad de optimización, explora las posibles soluciones para mejorar los tiempos de inicio:

  • Carga contenido de forma diferida y asíncrona para acelerar el TTID.
  • Minimiza las funciones de llamadas que realizan llamadas a Binder. Si son inevitables, para optimizar las llamadas, almacena los valores en caché en lugar de repetir las llamadas o traslada el trabajo sin bloqueo a los subprocesos en segundo plano.
  • Para que el inicio de tu app aparezca más rápido, puedes mostrar algún elemento que requiera un procesamiento mínimo para el usuario lo más rápido posible, hasta que el resto de la pantalla haya terminado de cargarse.
  • Crea y agrega un perfil de inicio a tu app.
  • Usa la biblioteca de inicio de apps de Jetpack para optimizar la inicialización de componentes durante el inicio de la app.

Analiza el rendimiento de la IU

El inicio de la app incluye una pantalla de presentación y el tiempo de carga de la página principal. Para optimizar el inicio de la app, inspecciona los seguimientos para comprender el tiempo que tarda en dibujarse la IU.

Limita el trabajo en la inicialización

Algunos fotogramas pueden tardar más en cargar que otros. Estos se consideran cargas costosas para la app.

Para optimizar la inicialización, haz lo siguiente:

  • Prioriza los pases de diseño lentos y úsalos para obtener mejoras.
  • Investiga cada advertencia de Perfetto y genera alertas de Systrace. Para ello, agrega eventos de registro personalizados para reducir los retrasos y los dibujos costosos.

Mide los datos de fotogramas

Existen varias maneras de medir los datos de fotogramas. Estos son los cinco métodos principales de colecciones:

  • Recopilación local con dumpsys gfxinfo: No todos los fotogramas observados en los datos de dumpsys son responsables de la renderización lenta de tu app ni afectan a los usuarios finales. Sin embargo, es bueno observar esta medida en diferentes ciclos de lanzamiento para comprender la tendencia general del rendimiento. Si deseas obtener más información sobre el uso de gfxinfo y framestats para integrar mediciones del rendimiento de la IU en tus prácticas de prueba, consulta Fundamentos de la prueba de apps para Android.
  • Recopilación de campos con JankStats: Recopila los tiempos de renderización de fotogramas de partes específicas de tu app con la biblioteca de JankStats, y registra y analiza los datos.
  • En pruebas que usan Macrobenchmark (Perfetto en el nivel interno)
  • FrameTimeline de Perfetto: En Android 12 (nivel de API 31), puedes recopilar métricas de cronograma de fotogramas desde un seguimiento de Perfetto para encontrar qué operación causa la caída de fotogramas. Este podría ser el primer paso para diagnosticar por qué hay una pérdida de fotogramas.
  • Generador de perfiles de Android Studio para la detección de bloqueos

Comprueba el tiempo de carga de la actividad principal

La actividad principal de tu app puede contener una gran cantidad de información que se carga desde varias fuentes. Verifica el diseño de la Activity de la página principal y observa específicamente el método Choreographer.onDraw de la actividad.

  • Usa reportFullyDrawn para informar al sistema que tu app está completamente cargada con fines de optimización.
  • Mide la actividad y los inicios de apps. Para ello, usa StartupTimingMetric con la biblioteca de Macrobenchmark.
  • Observa la pérdida de fotogramas.
  • Identifica diseños que tardan mucho tiempo en renderizarse o medirse.
  • Identifica los elementos que tardan mucho en cargarse.
  • Identifica los diseños innecesarios que se aumentan durante el inicio.

Considera las siguientes soluciones posibles para optimizar el tiempo de carga de la actividad principal:

  • Haz que tu diseño inicial sea lo más simple posible. Para obtener más información, consulta Cómo optimizar jerarquías de diseño.
  • Agrega puntos de seguimiento personalizados para brindar más información sobre los fotogramas perdidos y los diseños complejos.
  • Minimiza la cantidad y el tamaño de los recursos de mapas de bits cargados durante el inicio.
  • Usa ViewStub cuando los diseños no estén VISIBLE de inmediato. ViewStub es un objeto View invisible de tamaño cero que se puede usar para aumentar lentamente los recursos de diseño durante el tiempo de ejecución. Para obtener más detalles, consulta la información de ViewStub.

    Si usas Jetpack Compose, puedes obtener un comportamiento similar a ViewStub usando del estado para diferir la carga de algunos componentes:

    var shouldLoad by remember {mutableStateOf(false)}
    
    if (shouldLoad) {
     MyComposable()
    }
    

    Para cargar los elementos componibles dentro del bloque condicional, modifica shouldLoad:

    LaunchedEffect(Unit) {
     shouldLoad = true
    }
    

    Esto activa una recomposición que incluye el código dentro del bloque condicional en el primer fragmento.