Este tema te permite identificar y corregir problemas clave de rendimiento en tu app.
Problemas clave de rendimiento
Existen muchos problemas que pueden contribuir al rendimiento deficiente de una app, pero, a continuación, se explican algunas de las situaciones frecuentes que debes tener en cuenta en tu app:
- Bloqueo de desplazamiento
- "Bloqueo" es el término que se usa para describir el problema visual que se produce cuando el sistema no puede compilar ni proporcionar fotogramas a tiempo, de modo que se dibujen en la pantalla con la cadencia solicitada (60 Hz o superior). El bloqueo es más evidente durante el desplazamiento, cuando lo que debería ser un flujo animado sin interrupciones presenta problemas, en los que el movimiento se detiene a lo largo del proceso para uno o más fotogramas, ya que la app tarda más en procesar el 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 frecuencias de procesamiento tradicionales eran de 60 Hz, pero muchos dispositivos más recientes operan en el modo de 90 Hz durante las interacciones del usuario, como el desplazamiento, y algunos dispositivos admiten frecuencias incluso más altas, 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.
-
- 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.
Deberías fijar estos dos objetivos de inicio en tus apps:
Inicio en frío <500 ms: Un "inicio en frío" se produce 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 finalizan el proceso de la app.
Por el contrario, un "inicio semicaliente" se produce cuando la app ya se está ejecutando en segundo plano. Un 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. Establece como objetivo un inicio en frío que tarde 500 ms o menos.
Las latencias de p95 y p99 deberían estar muy cerca a la latencia mediana. Cuando la app a veces tarda mucho tiempo en iniciarse, la confianza del usuario disminuye. Las IPCs y las E/S innecesarias durante la ruta crítica del inicio de la app pueden experimentar contención de bloqueo e introducir estas inconsistencias.
Transiciones no fluidas
- Estas cuestiones surgen durante interacciones, como cambiar de pestañas o cargar una actividad nueva. Estos tipos de transiciones deben tener animaciones sin interrupciones y no deben incluir retrasos ni parpadeos visuales.
Ineficiencias energéticas
- Llevar a cabo una tarea consume 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 no solo se debe a que las asignaciones en sí exigen un esfuerzo de Android Runtime, sino que liberar esos objetos después ("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 eficaces de lo que solían ser, en especial, para los objetos temporales. Entonces, en los casos en que se solía indicar que se evite la asignación de objetos siempre que sea posible, ahora se recomienda hacer lo que tenga más sentido para tu app y tu arquitectura; ahorrar en asignaciones ante el riesgo de que se imposibilite el mantenimiento del código no es la opción correcta, debido a la capacidad de ART.
Sin embargo, todavía implica esfuerzo, por lo que vale la pena tener en cuenta si se asignaron muchos objetos en tu bucle interno, lo que podría contribuir a problemas de rendimiento.
Cómo identificar problemas
El flujo de trabajo recomendado para identificar y solucionar problemas de rendimiento es el siguiente:
- Identifica los recorridos críticos del usuario para inspeccionar. Estos pueden incluir los siguientes:
- 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 esos flujos mediante herramientas de depuración:
- Systrace o Perfetto: Te permiten ver, con exactitud, lo que sucede en todo el dispositivo con datos precisos de latencia.
- Generador de perfiles de memoria: Te permite verificar qué asignaciones de memoria se producen en el montón.
- Simpleperf: Observa un gráfico de qué llamadas a función consumen más CPU durante un período determinado. Cuando identificas algo que lleva mucho tiempo en Systrace, pero no sabes por qué, Simpleperf puede brindar información adicional.
La depuración manual de ejecuciones individuales de pruebas es fundamental para comprender y depurar estos problemas de rendimiento. Los pasos anteriores no se pueden reemplazar por el análisis de datos agregados. Sin embargo, configurar la recopilación de métricas en pruebas automatizadas y en el campo también es importante para comprender lo que los usuarios ven en realidad e identificar cuándo se producen regresiones:
- Flujos de inicio
- Métricas de campo: Tiempo de inicio de Play Console
- Pruebas de lab: Jetpack Macrobenchmark: inicio
- Bloqueo
- Métricas de campo
- Métricas de fotogramas de Play Console: Ten en cuenta que, en Play Console, no se pueden reducir las métricas a un recorrido específico del usuario, ya que todo lo que se informa es un bloqueo general en toda la app.
- Medición personalizada con
FrameMetricsAggregator
: Puedes usarFrameMetricsAggregator
para registrar las métricas de bloqueo durante un flujo de trabajo particular.
- Pruebas de lab
- Jetpack Macrobenchmark: desplazamiento
- Macrobenchmark recopila la latencia de fotogramas mediante 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
Cómo configurar tu app para el análisis del rendimiento
Una configuración correcta es fundamental para obtener comparativas precisas, repetibles y prácticas desde una aplicación. Realiza pruebas en un sistema que esté lo más cerca posible a la producción, y 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 aplicaciones pueden instrumentar su código con eventos de seguimiento personalizados.
Mientras se capturan los seguimientos, estos generan una sobrecarga pequeña (aproximadamente 5 μs) por sección, así que no los encierres en cada método. Solo el seguimiento de cantidades grandes de trabajo (>0.1 ms) puede brindar estadísticas significativas sobre los cuellos de botella.
Consideraciones del APK
Precaución: No midas el rendimiento en una compilación de depuración.
Las variantes de depuración pueden ser útiles para solucionar problemas y simbolizar muestras de pilas, pero tienen un impacto no lineal 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 los recursos que use tu aplicación, puede tener un impacto significativo en el rendimiento. Ten en cuenta que algunas configuraciones de ProGuard quitan los puntos de seguimiento; por lo tanto, considera quitar esas reglas para la configuración en la que ejecutas las pruebas.
Compilación
Compila tu aplicación en el dispositivo a un estado conocido (por lo general, velocidad o perfil de velocidad). La actividad JIT en segundo plano puede tener una sobrecarga significativa de rendimiento, y se generará con frecuencia si vuelves a instalar el APK entre las ejecuciones de prueba. El comando para hacerlo es el siguiente:
adb shell cmd package compile -m speed -f com.google.packagename
Con el modo de compilación "velocidad", se compilará la app por completo; con el modo "perfil de velocidad", se compilará la app, de acuerdo con un perfil de las instrucciones de código usadas 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 aquí:
/data/misc/profiles/ref/[package-name]/primary.prof
Ten en cuenta que 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 obtener microcomparativas. Entre otras cosas, estas secuencias de comandos hacen lo siguiente:
- Colocar las CPU a una frecuencia fija
- Inhabilitar los núcleos pequeños para configurar la GPU
- Inhabilitar la limitación térmica
No se recomienda para las pruebas que se enfocan en la experiencia del usuario (como el inicio de la app, las pruebas de DoU y las pruebas de bloqueos), pero puede ser fundamental 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 puedes observar en el siguiente seguimiento de ejemplo, un elemento activityStart
es seguido inmediatamente por otro activityStart
sin que la primera actividad dibuje ningún fotograma.
Puede suceder en un punto de entrada de una notificación y en un punto de entrada del inicio normal de la app, y, con frecuencia, se puede abordar mediante la 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 las recolecciones de elementos no utilizados (GC) se producen con más frecuencia de la esperada en Systrace.
En este caso, cada 10 segundos durante una operación de larga duración es un indicador de que tu app puede asignar de forma innecesaria pero consistente, con el tiempo:
También, puedes observar que una pila específica de llamadas 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 se puede producir cierta complejidad cuando se determina si es posible que un usuario haya visto, en última instancia, un fotograma descartado; en algunos casos, la plataforma puede "recuperar" un fotograma mediante el almacenamiento en búfer. Sin embargo, puedes ignorar la mayor parte de esa complejidad para identificar, con facilidad, 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, en realidad, observes una interrupción en esa cadencia regular, se trata de un fotograma con bloqueos:
Con un poco de práctica, podrás verlos con facilidad.
En algunos casos, deberás acercarte en ese punto de seguimiento para obtener más información sobre qué vistas se aumentan o qué está haciendo 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
- Invalida, de manera innecesaria, los datos de la copia de seguridad de
RecyclerView
. De esta manera, se pueden generar tiempos largos de procesamiento de fotogramas y, por lo tanto, bloqueos. En su lugar, invalida solo los datos que cambiaron, para minimizar la cantidad de vistas que deben actualizarse.- Consulta Cómo presentar datos dinámicos para conocer las maneras de evitar las llamadas costosas
notifyDatasetChanged()
, que causan que el contenido se actualice en lugar de reemplazarse por completo.
- Consulta Cómo presentar datos dinámicos para conocer las maneras de evitar las llamadas costosas
- No admite, de manera correcta, elementos
RecyclerView
anidados, lo que causa que el objetoRecyclerView
interno se vuelva a crear por completo todo el tiempo.- Cada objeto
RecyclerView
interno anidado debería tener un elementoRecycledViewPool
configurado para garantizar que las vistas se puedan reciclar entre los objetosRecyclerView
internos.
- Cada objeto
- No realiza una carga previa de los datos suficientes o no realiza una carga previa de forma oportuna. Puede ser un inconveniente para alcanzar, con rapidez, la parte inferior de una lista de desplazamiento, y es necesario esperar más datos del servidor. Si bien, técnicamente, no es un "bloqueo", ya que no pierde ningún plazo de fotogramas, puede tratarse de una mejora significativa en la UX para modificar los tiempos y la cantidad de carga previa, de manera que el usuario no tenga que esperar datos.
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