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 detallada de depuración para los recorridos críticos del usuario (CUJ) asociados con estos activadores.
Captura datos históricos
Muchos activadores requieren el análisis de los datos históricos previos al evento. El activador en sí suele ser una consecuencia de un problema, en lugar de la causa raíz. Si comienzas a crear 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). Para cuando el sistema detecta el error de ANR y le indica a la app, es posible que la operación ya haya finalizado. Si se inicia un perfil en ese momento, se omite el trabajo de bloqueo real.
Predecir con exactitud cuándo ocurrirán algunos activadores es inviable, lo que imposibilita 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 manualmente antes de que ocurran estos eventos. Los activadores de generación de perfiles se pueden usar para lo siguiente:
- Depura problemas de rendimiento: Diagnostica 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 estadísticas sobre los eventos, por ejemplo, las salidas de la app iniciadas por el usuario.
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 los siguientes pasos:
- Get the manager: Recupera el servicio
ProfilingManager. - Define a trigger: Compila un
ProfilingTriggerparaTRIGGER_TYPE_APP_FULLY_DRAWN. Este evento ocurre 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. - Registrar 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(). - Add triggers: Registra la lista de activadores con
ProfilingManagerusandoaddProfilingTriggers. - Fire event: 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 registro del sistema en segundo plano y que hay cuota disponible del limitador de frecuencia. Este paso opcional demuestra un flujo de extremo a extremo, ya que tu app debe llamar areportFullyDrawn()para este activador.
Cómo recuperar el registro
El sistema guarda los perfiles basados en activadores en el mismo directorio que otros perfiles. El nombre de archivo de los registros activados sigue este 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 registros, consulta Cómo recuperar y analizar datos de generación de perfiles.
Cómo funciona el rastreo en segundo plano
Para capturar datos de antes de un activador, el SO inicia periódicamente un registro en segundo plano. Si se activa un evento mientras este registro en segundo plano está activo y tu app está registrada para recibirlo, el sistema guarda el perfil de registro en el directorio de tu app. Luego, el perfil incluirá la información que condujo 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 la batería, el sistema no ejecuta registros en segundo plano de forma continua. En su lugar, usa un método de muestreo. El sistema inicia aleatoriamente un registro en segundo plano dentro de un período establecido (con una duración mínima y máxima). El espaciado aleatorio de estos registros 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 de anillo. Una vez que el búfer está lleno, los datos de registro nuevos sobrescriben los datos más antiguos. Como se muestra en la Figura 1, es posible que un registro capturado no abarque 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 condujo al activador.
Implementa límites de frecuencia específicos para los activadores
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 la limitación de frecuencia específica del activador.
ProfilingManager admite el límite de frecuencia específico del activador definido 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 espera específico para un activador. Una vez que venza el período de espera, podrás volver a activarla.
Depura activadores de forma local
Como los registros en segundo plano se ejecutan en momentos aleatorios, es difícil activar la depuración de forma local. Para forzar un registro de seguimiento en segundo plano para realizar pruebas, 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 registro continuo en segundo plano para el paquete especificado, lo que permite que cada activador pueda recopilar un perfil si el limitador de velocidad lo permite.
También puedes habilitar otras opciones de depuración, por ejemplo, inhabilitar el limitador de velocidad cuando depures de forma local. Para obtener más información, consulta Comandos de depuración para la generación de perfiles locales.