O ProfilingManager é compatível com a captura de perfis com base em acionadores do sistema. O
sistema gerencia o processo de gravação e fornece o perfil resultante ao
app.
Os gatilhos estão vinculados a eventos essenciais para o 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 gatilhos exigem a análise dos dados históricos que antecedem o evento. O gatilho em si costuma ser uma consequência de um problema, e não a causa raiz. Se você iniciar um perfil somente após o acionador ocorrer, a causa raiz já poderá ser perdida.
Por exemplo, uma operação de longa duração na linha de execução de interface causa um erro O app não está respondendo (ANR). Quando o sistema detecta o ANR e sinaliza o app, a operação pode já ter sido concluída. Iniciar um perfil nesse momento perde o trabalho de bloqueio real.
É impossível prever exatamente quando alguns acionadores vão ocorrer, o que impede o início manual de um perfil com antecedência.
Por que usar a captura baseada em acionadores?
O principal motivo para usar gatilhos 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 gatilhos de criação de perfil podem ser usados para:
- Depurar problemas de desempenho:diagnostique ANRs, vazamentos de memória e outros problemas de estabilidade.
- Otimizar jornadas ideais do usuário:analise e melhore os fluxos, por exemplo, a inicialização do app.
- Entender o comportamento do usuário:receba insights sobre eventos, como saídas do app iniciadas pelo usuário.
Configurar um gatilho
O código a seguir demonstra como se registrar para o
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 as seguintes etapas:
- Acessar o gerenciador: recupera o serviço
ProfilingManager. - Definir um gatilho: cria um
ProfilingTriggerparaTRIGGER_TYPE_APP_FULLY_DRAWN. Esse evento ocorre quando o app informa que concluiu 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 registre 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. - Disparar evento: chama
reportFullyDrawn(), que emite o eventoTRIGGER_TYPE_APP_FULLY_DRAWNpara o sistema, acionando uma coleta de perfil, supondo que um rastreamento em segundo plano do sistema esteja em execução e que haja cota de limitador de taxa disponível. Esta etapa opcional demonstra um fluxo completo, porque seu app precisa chamarreportFullyDrawn()para esse acionador.
Recuperar o trace
O sistema salva os perfis baseados em acionadores no mesmo diretório que os outros perfis. O nome de arquivo para traces acionados segue este formato:
profile_trigger_<profile_type_code>_<datetime>.<profile-type-name>
É possível extrair o arquivo usando o ADB. Por exemplo, para extrair o rastreamento do sistema capturado com o exemplo de código usando o ADB, o comando 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 gatilho ocorrer enquanto esse rastro em segundo plano estiver ativo e seu app estiver registrado para ele, o sistema vai salvar o perfil de rastro no diretório do app. O perfil vai incluir as informações que levaram ao acionador.
Depois que o perfil é salvo, o sistema notifica o 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 em segundo plano continuamente. Em vez disso, ele usa um método de amostragem. O sistema inicia aleatoriamente um rastro em segundo plano dentro de um período definido (com uma duração mínima e máxima). Espaçar esses traces de forma aleatória melhora a cobertura do acionador.
Os perfis acionados pelo sistema têm um tamanho máximo definido pelo sistema, então usam um buffer circular. Quando o buffer está cheio, os novos dados de trace substituem os mais antigos. Como mostrado na Figura 1, um rastro capturado pode não cobrir toda a duração da gravação em segundo plano se o buffer ficar cheio. Em vez disso, ele representa a atividade mais recente que levou ao acionador.
Implementar limitação de taxa específica do acionador
Os 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 funciona o limitador de taxa. Para evitar que um único tipo de gatilho esgote sua cota, implemente uma limitação de taxa específica para cada gatilho.
O ProfilingManager oferece suporte à limitação de taxa específica de acionadores definida pelo app. Isso
permite adicionar outra camada de limitação com base no tempo, além do limitador de taxa
atual. Use a API setRateLimitingPeriodHours para definir um tempo de espera específico para um gatilho. Depois que o tempo de espera expirar, será possível acioná-lo
novamente.
Depurar gatilhos localmente
Como os traces em segundo plano são executados em momentos aleatórios, é difícil depurar gatilhos localmente. Para forçar um trace em segundo plano para testes, 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 contínuo em segundo plano para o pacote especificado, permitindo que cada acionador colete um perfil se o limitador de taxa permitir.
Também é possível 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.