Este documento te permite identificar y corregir problemas clave de rendimiento en tu app.
Problemas clave de rendimiento
Existen muchos problemas que pueden ser causas del rendimiento deficiente de una app; pero, a continuación, se explican algunos problemas habituales que debes tener en cuenta en tu app:
- Latencia de inicio
La latencia de inicio es la cantidad de tiempo que se tarda entre el momento en que se presiona el ícono de la app, la notificación o algún otro punto de entrada, y el momento en que se muestran los datos del usuario en la pantalla.
Apunta a los siguientes objetivos de inicio en tus apps:
Inicio en frío en menos de 500 ms. Un inicio en frío ocurre cuando la app que se inicia no está presente en la memoria del sistema. Ocurre cuando se inicia la app por primera vez desde el reinicio o desde el momento en que el usuario o el sistema detienen el proceso de la app.
Por el contrario, un inicio semicaliente se produce cuando la app ya se está ejecutando en segundo plano. El inicio en frío requiere la mayor cantidad de trabajo del sistema, ya que tiene que cargar todo desde el almacenamiento e inicializar la app. Intenta que los inicios en frío tarden 500 ms o menos.
Latencias de p95 y p99 muy cercanas a la latencia mediana. Cuando la app tarda mucho tiempo en iniciarse, la experiencia del usuario es deficiente. Las comunicaciones entre procesos (IPC) y las E/S innecesarias durante la ruta de acceso crítica del inicio de la app pueden experimentar contención de bloqueo e introducir incoherencias.
- Bloqueo de desplazamiento
Bloqueo es el término que describe el problema visual que se produce cuando el sistema no puede compilar ni proporcionar fotogramas a tiempo para dibujarlos en la pantalla a la cadencia solicitada de 60 Hz o más. El bloqueo es más evidente durante el desplazamiento, ya que, en lugar de ver un flujo animado y fluido, se producen errores. El bloqueo aparece cuando se detiene el movimiento durante el proceso de uno o más fotogramas, ya que la app tarda más en renderizar contenido que la duración de un fotograma en el sistema.
Las apps deben fijar como objetivo frecuencias de actualización de 90 Hz. Las tasas de renderización convencionales son de 60 Hz, pero muchos dispositivos más nuevos operan en el modo de 90 Hz durante las interacciones del usuario, como el desplazamiento. Algunos dispositivos admiten frecuencias aún más altas, de hasta 120 Hz.
Para verificar qué frecuencia de actualización usa un dispositivo en un momento determinado, habilita una superposición en Opciones para desarrolladores > Frecuencia de actualización en la sección Depuración.
- Transiciones no fluidas
Esto se hace evidente durante interacciones, como cambiar de pestaña o cargar una actividad nueva. Estos tipos de transiciones deben tener animaciones fluidas y no deben incluir retrasos ni parpadeos visuales.
- Ineficiencias energéticas
Llevar a cabo una tarea reduce la carga de la batería, y realizar una tarea innecesaria reduce la duración de la batería.
Las asignaciones de memoria, que provienen de la creación de objetos nuevos en el código, pueden ser la causa de una tarea significativa en el sistema. Esto se debe a que las asignaciones en sí no solo requieren esfuerzo de Android Runtime (ART), sino que liberar esos objetos más tarde (recolección de elementos no utilizados) también requiere tiempo y esfuerzo. La asignación y la recolección son mucho más rápidas y eficientes, en especial, para los objetos temporales. Aunque solía ser una práctica recomendada evitar la asignación de objetos siempre que fuera posible, sugerimos hacer lo que tenga más sentido para tu app y arquitectura. Ahorrar en asignaciones ante el riesgo de que se imposibilite el mantenimiento del código no es la práctica recomendada, debido a la capacidad de ART.
Sin embargo, requiere esfuerzo, así que ten en cuenta que puede ser uno de los factores de problemas de rendimiento si asignas muchos objetos en tu bucle interno.
Identifica los problemas
Recomendamos el siguiente flujo de trabajo para identificar y solucionar problemas de rendimiento:
- Identifica e inspecciona los siguientes recorridos críticos del usuario:
- Flujos de inicio frecuentes, lo que incluye el selector y la notificación
- Cualquier pantalla en la que el usuario se desplace por los datos
- Transiciones entre pantallas
- Flujos de larga duración, como la navegación o la reproducción de música
- Inspecciona lo que sucede durante los flujos anteriores con las siguientes herramientas de depuración:
- Perfetto: Te permite ver lo que sucede en todo el dispositivo. con datos precisos de latencia.
- Generador de perfiles de memoria: Te permite ver qué asignaciones de memoria se producen. en el montón.
- Simpleperf: Muestra un gráfico de qué llamadas a función usan más CPU durante un período determinado. Cuando identificas algo Esto lleva mucho tiempo en Systrace, pero no sabes por qué. Simpleperf puede proporcionar información adicional.
Para comprender y depurar estos problemas de rendimiento, es fundamental depurar de forma manual ejecuciones individuales de pruebas. No puedes reemplazar los pasos anteriores con el análisis de datos agregados. Sin embargo, para comprender lo que los usuarios ven realmente e identificar cuándo se pueden producir regresiones, es importante configurar la recopilación de métricas en pruebas automatizadas y en el campo:
- Flujos de inicio
- Métricas de campo: Tiempo de inicio de Play Console
- Pruebas de lab: Prueba de inicio con Macrobenchmark
- Bloqueo
- Métricas de campo
- Métricas de fotogramas de Play Console: En Play Console, no puedes limitar las métricas a un recorrido específico del usuario. Solo informa los bloqueos generales en toda la app.
- Medición personalizada con
FrameMetricsAggregator
: Puedes usarFrameMetricsAggregator
para registrar las métricas de bloqueo durante una instancia en el flujo de trabajo.
- Pruebas de lab
- Desplazamiento con macrocomparativas.
- La macrocomparativa recopila la latencia de fotogramas con comandos
dumpsys gfxinfo
que encierran un solo recorrido del usuario. Esta es una manera razonable de comprender la variación en el bloqueo de un recorrido específico del usuario. Las métricasRenderTime
, que destacan el tiempo que tarda en dibujarse un fotograma, son más importantes que el recuento de fotogramas con bloqueos para identificar regresiones o mejoras.
- Métricas de campo
Problemas de verificación de los vínculos de apps
Los Vínculos de la aplicación son vínculos directos basados en la URL de tu sitio web que se verificaron que pertenecen a tu sitio web. Los siguientes son motivos que pueden causar fallas en las verificaciones de App Link.
- Alcances de filtros de intents: Solo agrega
autoVerify
a los filtros de intents para las URLs a las que puede responder tu app. - Interruptores de protocolo no verificados: redireccionamientos de subdominio y del servidor no verificados
se consideran riesgos de seguridad
y fallan la verificación. Hacen que fallen todos los vínculos
autoVerify
. Por ejemplo, redireccionar vínculos de HTTP a HTTPS, como example.com a www.example.com, sin verificar los vínculos HTTPS puede provocar que la verificación falle. Asegúrate de agregar un intent para verificar los vínculos de apps filtros. - Vínculos no verificables: Agregar vínculos no verificables para realizar pruebas provocará que el sistema no verifique los vínculos de apps para tu app.
- Servidores poco confiables: Asegúrate de que tus servidores puedan conectarse a las apps cliente.
Cómo configurar tu app para el análisis del rendimiento
Es esencial realizar la configuración adecuada para obtener comparativas precisas, repetibles y prácticas desde una app. Realiza pruebas en un sistema que esté lo más cerca posible de la producción y, al mismo tiempo, suprime las fuentes de ruido. En las siguientes secciones, se explica una serie de pasos específicos del APK y del sistema que puedes seguir para preparar una configuración de prueba, algunos de los cuales son específicos de casos de uso.
Puntos de seguimiento
Las apps pueden instrumentar su código con eventos de seguimiento personalizados.
Mientras se capturan los seguimientos, estos generan una pequeña sobrecarga de aproximadamente 5 μs por sección, así que no los encierres en cada método. Realizar un seguimiento de grandes fragmentos de trabajo de más de 0.1 ms puede proporcionar estadísticas significativas sobre los cuellos de botella.
Consideraciones del APK
Las variantes de depuración pueden ser útiles para solucionar problemas y simbolizar muestras de pilas, pero tienen un impacto grave en el rendimiento. Los dispositivos que ejecutan Android 10 (nivel de API 29) y versiones posteriores pueden usar profileable android:shell="true"
en su manifiesto para habilitar la generación de perfiles en las compilaciones de lanzamiento.
Usa la configuración de reducción de código de nivel de producción. Según el recursos que usa tu app, puede tener un impacto significativo en el rendimiento. Algunos Las configuraciones de ProGuard eliminan los puntos de seguimiento; considera eliminar esas reglas para la configuración en la que ejecutas las pruebas.
Compilación
Compila tu app en el dispositivo a un estado conocido (generalmente, speed
o speed-profile
). La actividad justo a tiempo en segundo plano (JIT) puede tener un nivel significativo
y alcanzarlos a menudo si reinstalas el APK
entre las ejecuciones de prueba. El siguiente es un comando para hacerlo:
adb shell cmd package compile -m speed -f com.google.packagename
El modo de compilación speed
compila la app por completo. El modo speed-profile
compila la app según un perfil de las rutas de código utilizadas que se recopilan durante el uso de la app. Puede ser difícil recopilar perfiles de manera coherente y correcta, por lo que, si decides usarlos, confirma que recopilen lo que esperas. Los perfiles se encuentran en la siguiente ubicación:
/data/misc/profiles/ref/[package-name]/primary.prof
Macrobenchmark te permite directamente especificar el modo de compilación.
Consideraciones del sistema
Para las mediciones de bajo nivel y alta fidelidad, calibra los dispositivos. Ejecuta las comparaciones A/B en el mismo dispositivo y la misma versión de SO. Se pueden producir variaciones significativas en el rendimiento, incluso en el mismo tipo de dispositivo.
En dispositivos con derechos de administrador, considera usar una secuencia de comandos lockClocks
para lo siguiente:
Microcomparativas Entre otras cosas, estas secuencias de comandos hacen lo siguiente:
- Colocar las CPU a una frecuencia fija
- Inhabilitar los núcleos pequeños y configurar la GPU
- Inhabilitar la limitación térmica
No recomendamos usar una secuencia de comandos lockClocks
para las pruebas enfocadas en la experiencia del usuario, como el inicio de la app, las pruebas de DoU y las pruebas de bloqueos, pero puede ser esencial para reducir el ruido en las pruebas de microcomparativas.
Cuando sea posible, considera usar un marco de trabajo de prueba, como Macrobenchmark, que puede reducir el ruido en las mediciones y evitar una medición incorrecta.
Inicio lento de la app: actividad innecesaria de trampolín
Una actividad de trampolín puede extender el tiempo de inicio de la app de manera innecesaria, y es importante que sepas si tu app lo está haciendo. Como se muestra en el siguiente seguimiento de ejemplo, un activityStart
es seguido inmediatamente por otro activityStart
sin que la primera actividad dibuje ningún fotograma.
Esto puede suceder en un punto de entrada de una notificación y en un punto de entrada de inicio normal de la app, y, a menudo, puedes abordarlo con una refactorización. Por ejemplo, si usas esa actividad para realizar la configuración antes de que se ejecute otra actividad, factoriza ese código en un componente o una biblioteca reutilizables.
Asignaciones innecesarias que activan GC frecuentes
Es posible que observes que se producen recolecciones de elementos no utilizados (GC) con mayor frecuencia de la esperada en Systrace.
En el siguiente ejemplo, cada 10 segundos durante una operación de larga duración es un indicador de que la app podría asignar de forma innecesaria pero consistente a lo largo del tiempo:
También puedes notar que una pila de llamadas específica realiza la gran mayoría de las asignaciones cuando se usa el Generador de perfiles de memoria. No es necesario que elimines todas las asignaciones de forma activa, ya que, de esta manera, se puede dificultar el mantenimiento del código. En cambio, comienza por trabajar en los hotspots de las asignaciones.
Fotogramas con bloqueos
La canalización de gráficos es relativamente complicada, y puede haber algún matiz para determinar si un usuario finalmente podría ver un fotograma descartado. En algunos casos, la plataforma puede "rescatar" un fotograma desde el almacenamiento en búfer. Sin embargo, puedes ignorar la mayor parte de esa complejidad para identificar los fotogramas problemáticos desde la perspectiva de tu app.
Cuando se dibujan fotogramas con poco trabajo requerido de la app, los puntos de seguimiento Choreographer.doFrame()
se producen en una cadencia de 16.7 ms (si se supone que es dispositivo con 60 FPS):
Si te alejas y navegas por el seguimiento, con frecuencia, observarás que los fotogramas tardan un poco más en completarse, pero está bien porque no tardan más de su tiempo asignado de 16.7 ms:
Cuando observes una interrupción en esta cadencia regular, se tratará de un fotograma con bloqueos, como se muestra en la figura 5:
Puedes practicar identificarlos.
En algunos casos, debes hacer zoom en un punto de seguimiento para obtener más información sobre qué vistas se aumentan o qué hace RecyclerView
. En otros casos, es posible que debas realizar una inspección más detallada.
Para obtener más información sobre la identificación de fotogramas con bloqueos y la depuración de sus causas, consulta Renderización lenta.
Errores comunes de RecyclerView
La invalidación innecesaria de todos los datos de copia de seguridad de RecyclerView
puede
prolonga los tiempos de renderización de fotogramas y los bloqueos. En cambio, para minimizar la cantidad de
que deben actualizarse, invalidan solo los datos que cambian.
Consulta Cómo presentar datos dinámicos para conocer las formas de evitar llamadas costosas a notifyDatasetChanged()
, que causan que el contenido se actualice en lugar de reemplazarlo por completo.
Si no admites todos los RecyclerView
anidados correctamente, es posible que el RecyclerView
interno se vuelva a crear por completo cada vez. Cada RecyclerView
interno anidado debe tener un RecycledViewPool
configurado para garantizar que las vistas se puedan reciclar entre cada RecyclerView
interno.
Si no se realiza la carga previa de los datos suficientes o no se realiza la carga previa de manera oportuna, es posible que llegar al final de una lista de desplazamiento resulte un inconveniente cuando un usuario necesita esperar más datos del servidor. Aunque, técnicamente, esto no es un bloqueo, ya que no se pierde ningún plazo de fotogramas, puedes mejorar significativamente la UX modificando el tiempo y la cantidad de carga previa de modo que el usuario no tenga que esperar datos.
Cómo depurar tu app
A continuación, se muestran diferentes métodos para depurar el rendimiento de tu app. Mira el siguiente video para obtener una descripción general del registro del sistema y el uso del generador de perfiles de Android Studio.
Cómo depurar el inicio de una app con Systrace
Consulta Tiempo de inicio de la app para obtener una descripción general del proceso de inicio de la app y mira el siguiente video para obtener una descripción general del seguimiento del sistema.
Puedes desambiguar los tipos de inicio en las siguientes etapas:
- Inicio en frío: Comienza por crear un proceso nuevo sin estado guardado.
- Inicio semicaliente: Recrea la actividad mientras se reutiliza el proceso o recrea el proceso con el estado guardado.
- Inicio en caliente: Reinicia la actividad y comienza en la inflación.
Te recomendamos que captures registros de Systraces con la app de Registro del sistema en el dispositivo. En Android 10 y versiones posteriores, usa Perfetto. En Android 9 y versiones anteriores, usa Systrace. También recomendamos visualizar los archivos de registro con el método basado en la Web Lector de registros de Perfetto. Para obtener más información, consulta Descripción general del sistema seguimiento.
Algunos aspectos que debes tener en cuenta son los siguientes:
- Contención del monitor: La competencia por los recursos protegidos por supervisión puede generar un retraso importante en el inicio de la app.
Transacciones de Binder síncronas: Busca transacciones innecesarias en la ruta crítica de tu app. Si una transacción necesaria es costosa, considera trabajar con el equipo de plataforma asociado para realizar mejoras.
GC simultánea: es común y tiene un impacto relativamente bajo, pero si ten en cuenta que lo experimentas con frecuencia, considera hacerlo con la memoria de Android Studio generador de perfiles.
E/S: Comprueba las E/S realizadas durante el inicio y busca bloqueos largos.
Actividad significativa en otros subprocesos, que pueden interferir en el subproceso de IU así que presta atención al trabajo en segundo plano durante el inicio.
Te recomendamos que llames a reportFullyDrawn
cuando se complete el inicio desde
la perspectiva de la app para mejorar los informes de métricas de inicio de la app Consulta la Hora
a pantalla completa para obtener más información sobre el uso de reportFullyDrawn
.
Puedes extraer las horas de inicio definidas por RFD a través del procesador de registros de Perfetto.
y se emite un evento de seguimiento visible para el usuario.
Usa Registro del sistema en el dispositivo
Puedes usar la app a nivel del sistema llamada Registro del sistema para capturar un registro del sistema en un dispositivo. Esta app te permite capturar registros desde el dispositivo sin
tener que enchufarlo o conectarlo a adb
.
Cómo usar el Generador de perfiles de memoria de Android Studio
Puedes usar el Generador de perfiles de memoria de Android Studio para inspeccionar la presión de memoria que podría deberse a fugas de memoria o patrones de uso inadecuados. Proporciona una visualización en vivo de las asignaciones de objetos.
Puedes solucionar problemas de memoria en tu app siguiendo la información del uso de la Generador de perfiles de memoria para hacer un seguimiento de por qué y con qué frecuencia se producen GC
Para generar perfiles de la memoria de la app, sigue estos pasos:
Detecta problemas de memoria.
Graba una sesión de perfilamiento de memoria del recorrido del usuario en el que deseas enfocarte. Busca un recuento de objetos creciente, como se muestra en la figura 7, que, en última instancia, genera GC, como se muestra en la figura 8.
Después de identificar el recorrido del usuario que agrega presión de memoria, analiza las causas raíz de la presión de memoria.
Diagnostica hotspots de presión de memoria.
Selecciona un rango en el cronograma para visualizar las asignaciones y Shallow Size, como se muestra en la Figura 9
Existen varias maneras de ordenar estos datos. Los siguientes son algunos ejemplos de la forma en que cada vista puede ayudarte a analizar los problemas.
Arrange by class: Es útil cuando quieres encontrar clases que son generar objetos que, de otro modo, se almacenan en caché o se reutilizan desde un grupo de memoria.
Por ejemplo, si ves una aplicación que crea 2,000 objetos de clase llamados “Vertex” cada segundo, aumenta el recuento de Allocations en 2,000 cada segundo y lo verás cuando ordenes por clase. Si quieres reutilizar estos objetos para evitar la generación de elementos no utilizados, implementa un grupo de memoria.
Organizar por pila de llamadas: Es útil cuando quieres encontrar dónde hay una ruta de acceso reciente en la que se asigna memoria, como dentro de un bucle o dentro de una función específica que realiza muchas tareas de asignación.
Shallow Size: Solo realiza un seguimiento de la memoria del objeto en sí. Es útil para el seguimiento de clases simples compuestas principalmente solo por valores primitivos.
Retained Size: Muestra la memoria total debido al objeto y las referencias. a la que solo hace referencia el objeto. Resulta útil para hacer un seguimiento de la presión de memoria causada por objetos complejos. Para obtener este valor, toma una memoria completa con el comando dump, como se muestra en la figura 10, y Retained Size se agrega como columna, como en la figura 11.
Mide el impacto de una optimización.
Las GC son más evidentes y fáciles de medir el impacto de las optimizaciones de memoria. Cuando una optimización reduce la presión de la memoria, verás menos recolecciones de elementos no utilizados.
Para medir el impacto de la optimización, mide el tiempo entre las GC en el cronograma del Generador de perfiles. Verás que tarda más entre GC.
Los principales impactos de las mejoras de memoria son los siguientes:
- Es probable que los cierres por memoria insuficiente se reduzcan si la app no cambia la presión de la memoria.
- Una menor cantidad de recolecciones de elementos no utilizados mejora las métricas de bloqueo, en especial en el P99. Esto se debe a que las GCs generan contención de CPU, lo que puede provocar el diferimiento de las tareas en procesamiento durante la recolección.
Recomendaciones para ti
- Nota: El texto del vínculo se muestra cuando JavaScript está desactivado
- Optimización y análisis de inicio de la app {:#app-startup-analysis-optimization}
- Fotogramas congelados
- Cómo escribir una macrocomparativa