O ProfilingManager oferece suporte à captura de perfis com base em acionadores do sistema. O sistema gerencia o processo de gravação e fornece o perfil resultante ao seu app.
Os acionadores estão vinculados a eventos críticos de desempenho. Os perfis gravados pelo sistema fornecem informações detalhadas de depuração para as jornadas ideais do usuário (CUJs) associadas a esses acionadores.
Capturar dados históricos
Muitos acionadores exigem a análise dos dados históricos que levam ao evento. O acionador em si geralmente é uma consequência de um problema, e não a causa raiz. Se você iniciar um perfil somente após a ocorrência do acionador, a causa raiz já poderá ser perdida.
Por exemplo, uma operação de longa duração na linha de execução da interface do usuário causa um erro "O app não está respondendo" (ANR, na sigla em inglês). Quando o sistema detecta o ANR e sinaliza o app, a operação já pode ter terminado. Iniciar um perfil nesse momento perde o trabalho de bloqueio real.
É inviável prever exatamente quando alguns acionadores ocorrem, o que impossibilita iniciar um perfil manualmente com antecedência.
Por que usar a captura baseada em acionadores?
O principal motivo para usar acionadores de criação de perfil é capturar dados de eventos imprevisíveis em que é impossível para um app começar a gravar manualmente antes que esses eventos ocorram. Os acionadores de criação de perfil podem ser usados para:
- Depurar problemas de desempenho:diagnosticar ANRs, vazamentos de memória e outros problemas de estabilidade.
- Otimizar jornadas ideais do usuário:analisar e melhorar fluxos, por exemplo, a inicialização do app.
- Entender o comportamento do usuário:receber insights sobre eventos, por exemplo, saídas do app iniciadas pelo usuário.
Configurar um acionador
O código a seguir demonstra como se registrar no acionador TRIGGER_TYPE_APP_FULLY_DRAWN e aplicar a limitação de taxa a ele.
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); }
O código executa estas etapas:
- Receber o gerenciador: recupera o serviço
ProfilingManager. - Definir um acionador: cria um
ProfilingTriggerparaTRIGGER_TYPE_APP_FULLY_DRAWN. Esse evento ocorre quando o app informa que terminou a inicialização e está interativo. - Definir limites de taxa: aplica um limite de taxa de 1 hora a esse acionador específico
(
setRateLimitingPeriodHours(1)). Isso impede que o app grave mais de um perfil de inicialização por hora. - Registrar listener: chama
registerForAllProfilingResultspara definir o callback que processa o resultado. Esse callback recebe o caminho do perfil salvo porgetResultFilePath(). - Adicionar acionadores: registra a lista de acionadores com
ProfilingManagerusandoaddProfilingTriggers. - Acionar evento: chama
reportFullyDrawn(), que emite oTRIGGER_TYPE_APP_FULLY_DRAWNevento para o sistema, acionando uma coleta de perfil, supondo que um trace de segundo plano do sistema estava em execução e que há cota de limitador de taxa disponível. Essa etapa opcional demonstra um fluxo de ponta a ponta porque o app precisa chamarreportFullyDrawn()para esse acionador.
Recuperar o trace
O sistema salva perfis baseados em acionadores no mesmo diretório que outros perfis. O nome do arquivo para traces acionados segue este formato:
profile_trigger_<profile_type_code>_<datetime>.<profile-type-name>
Você pode extrair o arquivo usando o ADB. Por exemplo, para extrair o trace do sistema capturado com o código de exemplo usando o ADB, ele pode ser assim:
adb pull /data/user/0/com.example.sampleapp/files/profiling/profile_trigger_1_2025-05-06-14-12-40.perfetto-trace
Para detalhes sobre como visualizar esses traces, consulte Recuperar e analisar dados de criação de perfil.
Como funciona o rastreamento em segundo plano
Para capturar dados de antes de um acionador, o SO inicia periodicamente um trace em segundo plano. Se um acionador ocorrer enquanto esse trace de segundo plano estiver ativo e seu app estiver registrado nele, o sistema salvará o perfil de trace no diretório do app. O perfil incluirá informações que levaram ao acionador.
Depois que o perfil é salvo, o sistema notifica seu app usando o callback fornecido para registerForAllProfilingResults. Esse callback fornece o caminho para
o perfil capturado, que pode ser acessado chamando
ProfilingResult#getResultFilePath().
Para reduzir o impacto no desempenho do dispositivo e na duração da bateria, o sistema não executa traces de segundo plano continuamente. Em vez disso, ele usa um método de amostragem. O sistema inicia aleatoriamente um trace de segundo plano em um período definido (com uma duração mínima e máxima). O espaçamento aleatório desses traces melhora a cobertura do acionador.
Os perfis acionados pelo sistema têm um tamanho máximo definido pelo sistema, então eles usam um buffer de anel. Quando o buffer está cheio, os novos dados de trace substituem os mais antigos. Como mostrado na Figura 1, um trace capturado pode não cobrir toda a duração da gravação em segundo plano se o buffer for preenchido. Em vez disso, ele representa a atividade mais recente que leva ao acionador.
Implementar a limitação de taxa específica do acionador
Acionadores de alta frequência podem consumir rapidamente a cota do limitador de taxa do app. Para entender melhor o limitador de taxa, consulte Como o limitador de taxa funciona. Para evitar que um único tipo de acionador esgote sua cota, você pode implementar a limitação de taxa específica do acionador.
O ProfilingManager oferece suporte à limitação de taxa específica do acionador definida pelo app. Isso permite adicionar outra camada de limitação baseada em tempo, além do limitador de taxa atual. Use a setRateLimitingPeriodHours API para definir um período de espera específico
para um acionador. Depois que o período de espera expirar, você poderá acioná-lo novamente.
Depurar acionadores localmente
Como os traces de segundo plano são executados em horários aleatórios, é difícil depurar acionadores localmente. Para forçar um trace de segundo plano para teste, use o seguinte comando ADB:
adb shell device_config put profiling_testing system_triggered_profiling.testing_package_name <com.example.myapp>
Esse comando força o sistema a iniciar um trace de segundo plano contínuo para o pacote especificado, permitindo que cada acionador colete um perfil se o limitador de taxa permitir.
Você também pode ativar outras opções de depuração, por exemplo, desativar o limitador de taxa ao depurar localmente. Para mais informações, consulte Comandos de depuração para criação de perfil local.