ProfilingManager admite la captura de perfiles basados en activadores del sistema. El sistema administra el proceso de grabación y proporciona el perfil resultante a tu app.
Los activadores están vinculados a eventos críticos para el rendimiento. Los perfiles grabados por el sistema proporcionan información de depuración detallada para los recorridos críticos del usuario (CUJ) asociados con estos activadores.
Captura datos históricos
Muchos activadores requieren analizar los datos históricos que conducen al evento. El activador en sí suele ser una consecuencia de un problema, más que la causa raíz. Si inicias un perfil solo después de que se produce el activador, es posible que ya se haya perdido la causa raíz.
Por ejemplo, una operación de larga duración en el subproceso de IU provoca un error de Aplicación no responde (ANR). Cuando el sistema detecta el error de ANR y envía un indicador a la app, es posible que la operación ya haya finalizado. Si inicias un perfil en ese momento, se perderá el trabajo de bloqueo real.
Predecir con exactitud cuándo se producen algunos activadores no es factible, lo que hace imposible iniciar un perfil de forma manual con anticipación.
¿Por qué usar la captura basada en activadores?
El motivo principal para usar activadores de generación de perfiles es capturar datos de eventos impredecibles en los que es imposible que una app comience a grabar de forma manual antes de que ocurran estos eventos. Los activadores de generación de perfiles se pueden usar para lo siguiente:
- Depura problemas de rendimiento: Diagnostica errores de ANR, fugas de memoria y otros problemas de estabilidad.
- Optimiza los recorridos críticos del usuario: Analiza y mejora los flujos, por ejemplo, el inicio de la app.
- Comprende el comportamiento de los usuarios: Obtén información sobre los eventos, por ejemplo, las salidas de la app iniciadas por los usuarios.
Configura un activador
En el siguiente código, se muestra cómo registrar el activador TRIGGER_TYPE_APP_FULLY_DRAWN y aplicarle un límite de frecuencia.
Kotlin
fun recordWithTrigger() { val profilingManager = applicationContext.getSystemService(ProfilingManager::class.java) val triggers = ArrayList<ProfilingTrigger>() val triggerBuilder = ProfilingTrigger.Builder(ProfilingTrigger.TRIGGER_TYPE_APP_FULLY_DRAWN) .setRateLimitingPeriodHours(1) triggers.add(triggerBuilder.build()) val mainExecutor: Executor = Executors.newSingleThreadExecutor() val resultCallback = Consumer<ProfilingResult> { profilingResult -> if (profilingResult.errorCode == ProfilingResult.ERROR_NONE) { Log.d( "ProfileTest", "Received profiling result file=" + profilingResult.resultFilePath ) setupProfileUploadWorker(profilingResult.resultFilePath) } else { Log.e( "ProfileTest", "Profiling failed errorcode=" + profilingResult.errorCode + " errormsg=" + profilingResult.errorMessage ) } } profilingManager.registerForAllProfilingResults(mainExecutor, resultCallback) profilingManager.addProfilingTriggers(triggers) }
Java
public void recordWithTrigger() { ProfilingManager profilingManager = getApplicationContext().getSystemService( ProfilingManager.class); List<ProfilingTrigger> triggers = new ArrayList<>(); ProfilingTrigger.Builder triggerBuilder = new ProfilingTrigger.Builder( ProfilingTrigger.TRIGGER_TYPE_APP_FULLY_DRAWN); triggerBuilder.setRateLimitingPeriodHours(1); triggers.add(triggerBuilder.build()); Executor mainExecutor = Executors.newSingleThreadExecutor(); Consumer<ProfilingResult> resultCallback = new Consumer<ProfilingResult>() { @Override public void accept(ProfilingResult profilingResult) { if (profilingResult.getErrorCode() == ProfilingResult.ERROR_NONE) { Log.d( "ProfileTest", "Received profiling result file=" + profilingResult.getResultFilePath()); setupProfileUploadWorker(profilingResult.getResultFilePath()); } else { Log.e( "ProfileTest", "Profiling failed errorcode=" + profilingResult.getErrorCode() + " errormsg=" + profilingResult.getErrorMessage()); } } }; profilingManager.registerForAllProfilingResults(mainExecutor, resultCallback); profilingManager.addProfilingTriggers(triggers); }
El código realiza estos pasos:
- Obtén el administrador: Recupera el servicio
ProfilingManager. - Define un activador: Compila un
ProfilingTriggerparaTRIGGER_TYPE_APP_FULLY_DRAWN. Este evento se produce cuando la app informa que finalizó el inicio y es interactiva. - Establece límites de frecuencia: Aplica un límite de frecuencia de 1 hora a este activador específico (
setRateLimitingPeriodHours(1)). Esto evita que la app registre más de un perfil de inicio por hora. - Registra el objeto de escucha: Llama a
registerForAllProfilingResultspara definir la devolución de llamada que controla el resultado. Esta devolución de llamada recibe la ruta de acceso del perfil guardado a través degetResultFilePath(). - Agrega activadores: Registra la lista de activadores con
ProfilingManagerusandoaddProfilingTriggers. - Activar evento: Llama a
reportFullyDrawn(), que emite el eventoTRIGGER_TYPE_APP_FULLY_DRAWNal sistema, lo que activa una recopilación de perfiles, suponiendo que se estaba ejecutando un seguimiento en segundo plano del sistema y que hay una cuota de limitador de frecuencia disponible. Este paso opcional demuestra un flujo de extremo a extremo, ya que tu app debe llamar areportFullyDrawn()para este activador.
Recupera el registro
El sistema guarda los perfiles basados en activadores en el mismo directorio que los demás perfiles. El nombre de archivo de los seguimientos activados tiene el siguiente formato:
profile_trigger_<profile_type_code>_<datetime>.<profile-type-name>
Puedes extraer el archivo con ADB. Por ejemplo, para extraer el registro del sistema capturado con el código de ejemplo usando ADB, podría verse de la siguiente manera:
adb pull /data/user/0/com.example.sampleapp/files/profiling/profile_trigger_1_2025-05-06-14-12-40.perfetto-trace
Para obtener detalles sobre cómo visualizar estos seguimientos, consulta Recupera y analiza datos de generación de perfiles.
Cómo funciona el seguimiento en segundo plano
Para capturar datos de antes de un activador, el SO inicia periódicamente un seguimiento en segundo plano. Si se produce un activador mientras este registro en segundo plano está activo y tu app está registrada para él, el sistema guardará el perfil de registro en el directorio de tu app. Luego, el perfil incluirá información que llevó al activador.
Una vez que se guarda el perfil, el sistema notifica a tu app con la devolución de llamada proporcionada a registerForAllProfilingResults. Esta devolución de llamada proporciona la ruta de acceso al perfil capturado, al que se puede acceder llamando a ProfilingResult#getResultFilePath().
Para reducir el impacto en el rendimiento del dispositivo y la duración de batería, el sistema no ejecuta seguimientos en segundo plano de forma continua. En cambio, usa un método de muestreo. El sistema inicia de forma aleatoria un registro en segundo plano dentro de un período establecido (con una duración mínima y máxima). Espaciar estos seguimientos de forma aleatoria mejora la cobertura de los activadores.
Los perfiles activados por el sistema tienen un tamaño máximo definido por el sistema, por lo que usan un búfer circular. Una vez que el búfer está lleno, los datos de seguimiento nuevos reemplazan los más antiguos. Como se muestra en la figura 1, es posible que un registro capturado no cubra toda la duración de la grabación en segundo plano si se llena el búfer; en cambio, representa la actividad más reciente que conduce al activador.
Implementa un límite de frecuencia específico para cada activador
Los activadores de alta frecuencia pueden consumir rápidamente la cuota del limitador de frecuencia de tu app. Para comprender mejor el limitador de frecuencia, te recomendamos que consultes Cómo funciona el limitador de frecuencia. Para evitar que un solo tipo de activador agote tu cuota, puedes implementar un límite de frecuencia específico para cada activador.
ProfilingManager admite el límite de frecuencia específico de los activadores definidos por la app. Esto
te permite agregar otra capa de limitación basada en el tiempo, además del limitador de frecuencia
existente. Usa la API de setRateLimitingPeriodHours para establecer un tiempo de enfriamiento específico para un activador. Una vez que venza el tiempo de espera, podrás volver a activarlo.
Depura activadores de forma local
Debido a que los seguimientos en segundo plano se ejecutan en momentos aleatorios, es difícil depurar los activadores de forma local. Para forzar un seguimiento en segundo plano con fines de prueba, usa el siguiente comando de ADB:
adb shell device_config put profiling_testing system_triggered_profiling.testing_package_name <com.example.myapp>
Este comando obliga al sistema a iniciar un seguimiento continuo en segundo plano para el paquete especificado, lo que permite que cada activador pueda recopilar un perfil si el limitador de frecuencia lo permite.
También puedes habilitar otras opciones de depuración, por ejemplo, inhabilitar el limitador de frecuencia cuando depuras de forma local. Para obtener más información, consulta Comandos de depuración para la generación de perfiles local.