Mucha gente ha escrito artículos relacionados con la optimización del inicio de aplicaciones de Android, pero se centran principalmente en los cambios que se han realizado en el rendimiento de inicio, y pocos artículos hablan sobre cómo analizar e identificar el rendimiento de inicio de las aplicaciones.
Este artículo combinará mi experiencia personal con Perfetto para explicar cómo se mide el tiempo de inicio de las aplicaciones en el vehículo y, después de medir el tiempo de inicio, ¿cómo podemos analizar los cuellos de botella en el rendimiento?
Antes de analizar el rendimiento de inicio de la aplicación, primero comprendamos brevemente algo de sentido común básico sobre el tiempo de inicio de la aplicación en Android.
Hora de inicio de la aplicación
Hora de visualización inicial (TTID)
El tiempo hasta la visualización inicial (TTID), que es el tiempo desde que el sistema recibe la intención de inicio hasta que la aplicación muestra el primer cuadro de la interfaz, es el momento en que el usuario ve la interfaz de la aplicación.
Medir TTID
Cuando la aplicación completa todo el trabajo mencionado anteriormente, se puede ver el siguiente resultado de registro en logcat.
/system_process I/ActivityTaskManager: Displayed xxxx/.MainActivity: +401ms
Se muestran métricas de tiempo en la salida de Logcat antes de que todos los recursos se carguen y muestren por completo, omitiendo los recursos a los que no se hace referencia en el archivo de diseño o el momento en que la aplicación crea recursos como parte de la inicialización del objeto.
A veces, los registros en la salida de logcat contendrán un total de campos adicional. Como sigue:
ActivityManager: Displayed com.android.myexample/.StartupTiming: +3s534ms (total +1m22s643ms)
En este caso, la primera medición es sólo para la primera actividad sorteada. total
La medición del tiempo se mide desde que se inicia la aplicación, y puede incluir otra actividad que se inicia por primera vez pero que no muestra nada en pantalla. total
Las mediciones de tiempo solo se muestran si hay una diferencia entre el tiempo de la actividad individual y el tiempo total de inicio.
am start -S -W
El tiempo medido mediante los comandos introducidos en algunos blogs es en realidad el tiempo de visualización inicial. En la mayoría de los casos, el tiempo de visualización inicial no representa el tiempo de inicio real de la aplicación. Por ejemplo, cuando se inicia la aplicación, necesita sincronizar los datos más recientes de la red. El tiempo de inicio real es el tiempo que lleva cuando todos los Se cargan los datos. Este es el TTFD que se introducirá la próxima vez: Visualización total .
Tiempo hasta la visualización completa (TTFD)
El tiempo hasta la visualización completa (TTFD, The Time to Full Display). Es el momento desde que el sistema recibe la intención de inicio hasta que la aplicación termina de cargar todos los recursos y ver las jerarquías, es decir, cuando el usuario realmente puede usar la aplicación.
Medida TTFD
Método 1: llamarreportFullyDrawn()
reportFullyDrawn()
Se puede llamar al método después de que la aplicación haya terminado de cargar todos los recursos y ver las jerarquías, lo que le permite al sistema saber que la aplicación se ha mostrado por completo para que se pueda calcular el tiempo de visualización completo. Si no se llama a este método, el sistema solo puede calcular el TTID pero no el TTFD.
system_process I/ActivityTaskManager: Fully drawn xxxx/.MainActivity: +1s54ms
Método 2: desenmarcar
El método de división de cuadros es actualmente el método más común para calcular el tiempo de inicio de las aplicaciones en el vehículo. El método de división de cuadros tiene muchos métodos diferentes de grabación y división de cuadros.
Es común usar una cámara que admita 60 fps (también se pueden usar teléfonos móviles que admitan cámaras de 60 fps) para grabar el video de inicio de la aplicación y luego usar el reproductor de video para ver la diferencia en la cantidad de cuadros desde el clic del escritorioPotplay
. hasta que la pantalla de la aplicación se muestre completamente y luego divídala por 60. Puede obtener el tiempo de inicio de la aplicación.
El método anterior es adecuado para los evaluadores, aquí hay otro método que es más adecuado para los desarrolladores: la división de fotogramas FFmpeg.
Dirección de descarga de FFmpeg: http://ffmpeg.org/download.html?aemtn=tg-on
Primero use adb para conectarse al dispositivo Android y use el comando de grabación de pantalla para grabar el video cuando se inicie la aplicación.
adb shell screenrecord /sdcard/launch.mp4
Utilice FFmpeg para comprobar el número de fotograma de un vídeo
ffmpeg -i launch.mp4
Si la cantidad de cuadros de video es inferior a 60 fps, continúe usando FFmpeg para agregar cuadros al video a 60 fps.
ffmpeg -i launch.mp4 -filter:v fps=60 output.mp4
Divida cada cuadro del video lleno de cuadros en una imagen y luego calcule la diferencia en la cantidad de cuadros desde que hace clic en el escritorio hasta que se muestra completamente la pantalla de la aplicación .
ffmpeg -i output.mp4 output_%04d.jpg
O convierta el vídeo lleno de fotogramas en un GIF animado y utilice un navegador de imágenes para contar los fotogramas (el navegador de imágenes integrado de MAC OS servirá).
ffmpeg -i output.mp4 -vf fps=60,scale=320:-1:flags=lanczos -loop 0 output.gif
Esta es una introducción al tiempo de inicio de la aplicación y los métodos de medición. Para obtener más información, consulte el documento oficial de Android " Tiempo de inicio de la aplicación | Calidad de la aplicación | Android ", que es muy detallado.
Vale la pena mencionar que el tiempo de inicio promedio actual de las aplicaciones de vehículos convencionales (tomando la plataforma 8155 como ejemplo) es el siguiente:
- Arranque en frío TTFD
Las aplicaciones de Internet a gran escala de terceros deben controlarse por debajo de 2,6 segundos y las aplicaciones de sistemas de vehículos deben controlarse por debajo de 1,6 segundos.
- Arranque en caliente TTFD
Generalmente es necesario controlarlo por debajo de 0,8 s.
Lo anterior es mi experiencia personal: diferentes fabricantes de hosts definitivamente tendrán diferentes requisitos de rendimiento.
Introducción al Perfecto
Perfetto
Es una herramienta de seguimiento a nivel de sistema introducida en Android 10, compatible con Android, Linux y Chrome, y se utiliza para reemplazar Systrace
. En comparación con Profiler
y AGI
, ya no se limita a la aplicación, sino que puede proporcionar el estado de ejecución de todo el sistema. Cuando necesitamos ver si la aplicación afecta la estabilidad y fluidez del sistema, o por el contrario, se puede utilizar para analizar el impacto del sistema en la aplicación. Se puede utilizar Perfetto
para el seguimiento y análisis a nivel del sistema .
Para conocer el contenido básico de Perfetto, puede ver el video oficial de Android que traduje antes: [Traducción] Habilidades modernas de desarrollo de Android: introducción a Perfetto
Comience rápidamente con Perfetto
Perfetto
Hay muchas formas de usarlo y personalmente recomiendo usar record_android_trace
scripts. Es Perfetto
un script auxiliar proporcionado para ayudarnos a recopilar datos de rendimiento de dispositivos Android mediante adb. Este script hace lo siguiente:
- Detecta automáticamente si
perfetto
el binario está disponible en el dispositivo y, si no, intenta descargarlo desde GitHub y enviarlo al dispositivo. - Establezca automáticamente los parámetros de configuración de seguimiento, como el tiempo de seguimiento, el tamaño del búfer, la ruta del archivo de salida, etc.
- Ejecute automáticamente
perfetto
el comando y extraiga el archivo de salida a su computadora una vez que se complete el seguimiento. - Abre automáticamente archivos de salida en un navegador, lo que le permite ver y analizar los resultados del seguimiento.
Dirección de descarga de record_android_trace: https://raw.githubusercontent.com/google/perfetto/master/tools/record_android_trace
record_android_trace
El uso es el siguiente:
./record_android_trace [options] [category1] [category2] ...
Entre ellas, las opciones son algunos parámetros opcionales, como:
- -o OUT_FILE: especifique la ruta del archivo de salida. Si no se especifica, el valor predeterminado es perfetto_trace.pb.
- -t TIME: especifique el tiempo de seguimiento. Si no se especifica, el valor predeterminado es 10 segundos.
- -b TAMAÑO: especifica el tamaño del búfer de seguimiento. Si no se especifica, el valor predeterminado es 32 MB.
La categoría son algunas categorías atrace o ftrace que se van a rastrear. Puede usar –list para ver las categorías de seguimiento admitidas por el dispositivo. El resultado puede ser el siguiente:
link@link-PC:~/Desktop$ ./record_android_trace --list
gfx - Graphics
input - Input
view - View System
webview - WebView
wm - Window Manager
am - Activity Manager
sm - Sync Manager
audio - Audio
video - Video
camera - Camera
hal - Hardware Modules
res - Resource Loading
dalvik - Dalvik VM
rs - RenderScript
bionic - Bionic C Library
power - Power Management
pm - Package Manager
ss - System Server
database - Database
network - Network
adb - ADB
vibrator - Vibrator
aidl - AIDL calls
nnapi - NNAPI
rro - Runtime Resource Overlay
pdx - PDX services
sched - CPU Scheduling
irq - IRQ Events
i2c - I2C Events
freq - CPU Frequency
idle - CPU Idle
disk - Disk I/O
sync - Synchronization
workq - Kernel Workqueues
memreclaim - Kernel Memory Reclaim
regulators - Voltage and Current Regulators
binder_driver - Binder Kernel driver
binder_lock - Binder global lock trace
pagecache - Page cache
memory - Memory
thermal - Thermal event
gfx - Graphics (HAL)
ion - ION allocation (HAL)
Por ejemplo, si desea rastrear programación, gfx y ver, el archivo de salida es trace.perfetto-trace, el tiempo de rastreo es de 5 segundos y el tamaño del búfer es de 16 MB, puede ejecutar el siguiente comando:
./record_android_trace -o trace.perfetto-trace -t 5s -b 16mb sched gfx view
Perfetto analiza el rendimiento de las startups
Es muy sencillo utilizar Perfetto para analizar el rendimiento de inicio de la aplicación. Primero, utilice record_android_trace para capturar los datos de inicio de la aplicación y ejecutar las siguientes instrucciones:
./record_android_trace -o trace.perfetto-trace -t 15s -b 200mb gfx input view webview wm am sm audio video camera hal res dalvik rs bionic power pm ss database network adb vibrator aidl nnapi rro pdx sched irq i2c freq idle disk sync workq memreclaim regulators binder_driver binder_lock pagecache memory gfx ion
Después de 15 segundos, record_android_trace nos abrirá automáticamente el navegador. En una columna se Android App Startups
muestra la hora de inicio de la aplicación. Como se muestra a continuación, es importante tener en cuenta que Android App Startups
la hora que se muestra es el TTID de la aplicación : la hora de visualización inicial .
Seleccione Métricas en el lado izquierdo de Perfetto, luego seleccione android_startup , haga clic en Ejecutar, Perfetto nos ayudará automáticamente a analizar varios datos cuando se inicie la aplicación, como se muestra a continuación.
android_startup {
startup {
startup_id: 1
startup_type: "warm"
package_name: "com.xxx.xxx.weather"
process_name: "com.xxx.xxx.weather"
process {
name: "com.xxx.xxx.weather"
uid: 1000
pid: 3376
}
zygote_new_process: false
activity_hosting_process_count: 1
event_timestamps {
intent_received: 100680138137
first_frame: 102167532928
}
to_first_frame {
dur_ns: 1487394791
dur_ms: 1487.394791
main_thread_by_task_state {
running_dur_ns: 1316606193
runnable_dur_ns: 34121303
uninterruptible_sleep_dur_ns: 20429636
interruptible_sleep_dur_ns: 84415940
uninterruptible_io_sleep_dur_ns: 12221457
uninterruptible_non_io_sleep_dur_ns: 8208179
}
time_activity_manager {
dur_ns: 16070209
dur_ms: 16.070209
}
time_activity_start {
dur_ns: 97578437
dur_ms: 97.578437
}
time_activity_resume {
dur_ns: 833413073
dur_ms: 833.413073
}
time_choreographer {
dur_ns: 481555469
dur_ms: 481.555469
}
time_inflate {
dur_ns: 1241538748
dur_ms: 1241.538748
}
time_get_resources {
dur_ns: 6173178
dur_ms: 6.173178
}
time_verify_class {
dur_ns: 1675365
dur_ms: 1.675365
}
time_gc_total {
dur_ns: 82049531
dur_ms: 82.049531
}
time_dlopen_thread_main {
dur_ns: 15522344
dur_ms: 15.522344
}
time_lock_contention_thread_main {
dur_ns: 4711976
dur_ms: 4.711976
}
time_jit_thread_pool_on_cpu {
dur_ns: 375033124
dur_ms: 375.033124
}
time_gc_on_cpu {
dur_ns: 81314427
dur_ms: 81.314427
}
jit_compiled_methods: 218
other_processes_spawned_count: 6
}
verify_class {
name: "com.xxx.xxx.weather.service.VoiceActionManager"
dur_ns: 1675365
}
dlopen_file: "/system/priv-app/Weather/Weather.apk!/lib/arm64-v8a/libffavc.so"
dlopen_file: "/system/priv-app/Weather/Weather.apk!/lib/arm64-v8a/libpag.so"
dlopen_file: "/vendor/lib64/hw/[email protected]"
dlopen_file: "libadreno_utils.so"
dlopen_file: "/vendor/lib64/hw/[email protected]"
dlopen_file: "/vendor/lib64/hw/gralloc.msmnile.so"
dlopen_file: "libadreno_app_profiles.so"
dlopen_file: "libEGL_adreno.so"
system_state {
dex2oat_running: false
installd_running: false
broadcast_dispatched_count: 0
broadcast_received_count: 0
most_active_non_launch_processes: "media.codec"
most_active_non_launch_processes: "app_process"
most_active_non_launch_processes: "media.hwcodec"
most_active_non_launch_processes: "/vendor/bin/hw/vendor.qti.hardware.display.allocator-service"
most_active_non_launch_processes: "/system/bin/audioserver"
installd_dur_ns: 0
dex2oat_dur_ns: 0
}
slow_start_reason: "GC Activity"
slow_start_reason: "Main Thread - Time spent in Running state"
slow_start_reason: "Time spent in view inflation"
}
}
android_startup es una estructura de datos que se utiliza para registrar y analizar el rendimiento de inicio de las aplicaciones de Android y contiene diversa información durante el proceso de inicio de la aplicación, como tipo de inicio, hora de inicio, motivo de inicio, dependencias de inicio, estado del sistema, etc.
El contenido de android_startup es un texto en formato protobuf, que representa los datos de inicio de una aplicación meteorológica llamada com.xxx.xxx.weather. Entre ellos, el más importante es slow_start_reason en el último párrafo , que nos muestra las razones por las que la aplicación puede provocar un inicio lento, nos centraremos en el análisis en la tercera sección.
Los significados de otros campos son los siguientes:
startup_id: es un identificador único que indica que se trata del primer inicio;
startup_type: es un tipo de enumeración, lo que indica que se trata de un inicio en caliente, es decir, el proceso de solicitud ya existe, pero no hay actividad en primer plano;
nombre_paquete y nombre_proceso representan el nombre del paquete de la aplicación y el nombre del proceso;
Proceso: representa información sobre el proceso de la aplicación, incluido el nombre, el identificador de usuario (uid) y el identificador de proceso (pid);
zygote_new_process: indica si se crea un nuevo proceso a través de zygote, aquí es falso;
Activity_hosting_process_count: indica cuántas actividades se alojan en este proceso, aquí es 1;
event_timestamps: indica las marcas de tiempo de varios eventos, por ejemplo, intent_received indica la hora en que se recibe la intención de inicio y first_frame indica la hora en que se muestra el primer fotograma;
to_first_frame: indica el tiempo y los detalles desde que se recibe la intención de inicio hasta que se muestra el primer fotograma, incluido el tiempo total, el tiempo en varios estados del hilo principal, el tiempo de varias operaciones, el uso de varios recursos, etc.;
verificar_clase: indica información sobre la verificación de la carga de clases, incluido el nombre y la hora de la clase;
dlopen_file: indica información sobre cómo abrir archivos de biblioteca compartida, incluidos los nombres de los archivos;
system_state: información que indica el estado del sistema, incluido si dex2oat o installd se están ejecutando, si se envían o reciben transmisiones, qué procesos que no son de inicio son los más activos, etc.;
Práctica perfecta
Activar GC al iniciar
Fenómeno : "Actividad de GC" aparece en slow_start_reason , lo que indica que la actividad de GC ralentiza el inicio de la aplicación durante la fase de inicio.
Análisis : haga clic en [mostrar línea de tiempo] para regresar a la interfaz de la línea de tiempo de Perfetto. En la línea de tiempo de inicio, puede ver que hay un subproceso llamado HeapTaskDaemon , que es el subproceso GC de la aplicación. Estuvo activo durante aproximadamente 100 ms durante la fase de inicio, lo que provocó que la línea de tiempo de actividadResume se alargue en 100 ms. Para evitar que el fenómeno se descubra accidentalmente, realizamos múltiples mediciones y descubrimos que la actividad del GC definitivamente se activará cuando se inicie la aplicación. como muestra la imagen:
Motivo : Según el análisis posterior de la línea de tiempo, se descubrió que la aplicación cargará una fuente especial durante la fase de inicio. La fuente pesa aproximadamente 13 MB. Después de comunicarse con el desarrollo de la aplicación, se confirmó que la fuente se movió a la capa del sistema y la capa de aplicación no necesitan cargar la fuente. Después de eliminar la fuente, GC ya no se activa al 100% cuando se inicia la aplicación.
Operaciones que requieren mucho tiempo en el hilo principal
Fenómeno : "Subproceso principal: tiempo transcurrido en estado de ejecución" aparece en slow_start_reason , lo que significa que durante la fase de inicio, se realizan operaciones que consumen más tiempo en el subproceso principal.
Motivo : Esta situación es muy común durante el desarrollo de aplicaciones. Los desarrolladores de aplicaciones naturalmente colocarán algunas operaciones de adquisición de datos entre procesos bajo el método OnCreate o onStart de la actividad del hilo principal. Aunque estos métodos IPC no activarán ANR, ralentizarán el inicio de la aplicación. aplicación y debe colocarse en el grupo de subprocesos o corrutina para su ejecución.
OpenDexFilesFromOat lleva tiempo
Fenómeno : "Subproceso principal: tiempo invertido en OpenDexFilesFromOat*" aparece en slow_start_reason , lo que indica que se dedica más tiempo a leer archivos dex durante la fase de inicio.
Motivo : esta situación es más común en los sistemas Android de automóviles. Esto puede deberse a que el sistema ha modificado el proceso dex2oat en el sistema para acelerar el inicio, provocando este fenómeno, si no lleva mucho tiempo se puede ignorar.
Solo digo "posible" aquí porque el sistema operativo del automóvil actual tiene muchas modificaciones con respecto a Android nativo para poder comenzar rápidamente. Necesitamos hacer un análisis detallado basado en nuestra propia situación real.
Tiempo de espera de dibujo continuo de varios cuadros
Fenómeno: el tiempo de inicio de una aplicación en Perfetto no es largo, alrededor de 1,3 segundos, pero después de utilizar el método de división de fotogramas, se descubre que la aplicación se retrasará ligeramente después del inicio, lo que provocará que el tiempo de inicio real se extienda a 2,1. s. El rendimiento en Perfetto es el siguiente: después de dibujar el primer fotograma, el tiempo de dibujo de los siguientes 2, 4 y 5 fotogramas supera los 150 ms.
Análisis : la línea de tiempo de dibujo de cuadros proporcionada por Perfetto muestra que la mayor parte del tiempo se dedica al diseño de la vista, lo que muestra que después de dibujar el primer cuadro, se activan múltiples redibujos de página.
Motivo : Al combinar el código con el registro de la aplicación, se descubrió que la aplicación actualiza la página una vez con datos vacíos cuando se inicia y luego obtiene datos de la interfaz IPC para actualizar la página nuevamente. Además, debido a defectos del código, los datos La actualización se ejecutará 4 veces seguidas, lo que provocará esta situación. Modifique el código defectuoso para que ya no se produzca un tiempo de espera de dibujo continuo después del primer fotograma.
Referencias
https://developer.android.com/topic/performance/vitals/launch-time#time-initial