Otimização e análise da inicialização do app

Durante a inicialização do app, ele causa a primeira impressão nos usuários. É necessário que a inicialização seja rápida para carregar e mostrar informações que o usuário precisa para usar o app. Se ela demorar muito, os usuários poderão sair por estarem esperando por muito tempo.

Recomendamos usar a biblioteca Macrobenchmark para avaliar a inicialização. A biblioteca oferece uma visão geral e rastreamentos detalhados do sistema para mostrar exatamente o que está acontecendo durante a inicialização.

Os rastreamentos do sistema fornecem informações úteis sobre o que está acontecendo no dispositivo. Isso permite entender o que o app faz durante a inicialização e identificar as possíveis áreas de otimização.

Para analisar a inicialização do app, siga estas etapas:

Etapas para analisar e otimizar a inicialização

Durante a inicialização, os apps geralmente precisam carregar recursos específicos que são essenciais para os usuários finais. Recursos não essenciais podem aguardar o carregamento depois da conclusão da inicialização.

Para melhorar o desempenho, siga estas etapas:

  • Use a biblioteca Macrobenchmark para medir o tempo gasto por cada operação e identificar blocos que levam muito tempo para serem concluídos.

  • Confirme se a operação com uso intensivo de recursos é indispensável para a inicialização do app. Se a operação puder aguardar até o app ser totalmente carregado, isso poderá minimizar as restrições de recursos na inicialização.

  • Garanta que essa operação seja executada na inicialização do app. Muitas vezes, operações desnecessárias podem ser chamadas do código legado ou de bibliotecas de terceiros.

  • Se possível, mova operações de longa duração para o segundo plano. Os processos em segundo plano ainda podem afetar o uso da CPU durante a inicialização.

Depois de investigar a operação, você pode decidir entre o tempo necessário para carregar e a necessidade de incluí-la na inicialização do app. É importante incluir o potencial de regressão ou alterações interruptivas ao mudar o fluxo de trabalho do app.

Otimize e meça de novo até chegar a um tempo de inicialização satisfatório para o app. Para saber mais, consulte Usar métricas para detectar e diagnosticar problemas.

Medir e analisar o tempo gasto nas principais operações

Quando você tiver um rastreamento de inicialização completo do app, observe-o e meça a duração das principais operações, como bindApplication ou activityStart. Recomendamos o uso do Perfetto ou dos Android Studio Profilers para analisar esses rastreamentos.

Observe o tempo total da inicialização do app para identificar operações que:

  • gastem muito tempo e possam ser otimizadas. Cada milissegundo conta para o desempenho. Por exemplo, procure tempos de exibição Choreographer, tempos de inflação de layout, tempos de carregamento da biblioteca, transações Binder ou tempos de carregamento de recursos. Para começar, analise todas as operações que levam mais de 20 ms;
  • bloqueiem a linha de execução principal. Para saber mais, consulte Navegar em um relatório do Systrace;
  • não tenham a execução necessária na inicialização;
  • possam aguardar até que o primeiro frame seja renderizado.

Investigue cada um desses rastros para encontrar lacunas de desempenho.

Identificar operações caras na linha de execução principal

A prática recomendada é manter as operações caras, como as de E/S de arquivos e as de acesso à rede, fora da linha de execução principal. Isso é igualmente importante durante a inicialização do app, porque operações caras na linha de execução principal podem fazer com que o app não responda e podem atrasar outras operações importantes. A StrictMode.ThreadPolicy pode ajudar a identificar casos em que operações caras estão acontecendo na linha de execução principal. É recomendável ativar a classe StrictMode em builds de depuração para identificar problemas o quanto antes, conforme mostrado no exemplo abaixo:

Kotlin

class MyApplication : Application() {

    override fun onCreate() {
        super.onCreate()

        ...
        if (BuildConfig.DEBUG)
            StrictMode.setThreadPolicy(
                StrictMode.ThreadPolicy.Builder()
                    .detectAll()
                    .penaltyDeath()
                    .build()
            )
        ...
    }
}

Java

public class MyApplication extends Application {

    @Override
    public void onCreate() {
        super.onCreate();

        ...
        if(BuildConfig.DEBUG) {
            StrictMode.setThreadPolicy(
                    new StrictMode.ThreadPolicy.Builder()
                            .detectAll()
                            .penaltyDeath()
                            .build()
            );
        }
        ...
    }
}

O uso da StrictMode.ThreadPolicy ativa a política de linha de execução em todos os builds de depuração e causa uma falha no app sempre que violações da política de linha de execução são detectadas, o que faz com que seja fácil encontrar violações da política em linhas de execução.

TTID e TTFD

Para conferir o tempo que o app leva para produzir o primeiro frame, meça o tempo para exibição inicial (TTID, na sigla em inglês). No entanto, isso não reflete necessariamente o tempo até o usuário começar a interagir com o app. A métrica tempo para exibição total (TTFD, na sigla em inglês) é mais útil para medir e otimizar os caminhos de código necessários para ter um estado totalmente utilizável do app.

Para conferir estratégias de geração de relatórios quando a interface do app está totalmente renderizada, consulte Melhorar a precisão da marcação do tempo de inicialização.

Otimize para TTID e TTFD, porque ambos são importantes nas respectivas áreas. Um TTID baixo ajuda o usuário a saber que o app está sendo iniciado. Já manter o TTFD baixo é importante para garantir que o usuário possa começar a interagir com o app rapidamente.

Analisar o estado geral da linha de execução

Selecione o tempo de inicialização do app e observe as frações gerais da linha de execução. A linha de execução principal precisa ser responsiva o tempo todo.

Ferramentas como o Android Studio Profiler e Perfetto (em inglês) fornecem uma visão geral detalhada da linha de execução principal e de quanto tempo é gasto cada etapa. Para saber mais sobre a visualização de rastros do Perfeito, consulte IU do Perfetto (link em inglês) na documentação do Google Cloud.

Identificar os principais trechos do estado de suspensão da linha de execução principal

Se muito tempo é gasto em suspensão, é provável que a linha de execução principal do app esteja aguardando a conclusão do trabalho. Se você tem um app com várias linhas de execução, identifique aquela em que a principal está aguardando e otimize essas operações. Isso também ajuda a garantir que não haja contenção de bloqueio desnecessária causando atrasos no caminho crítico.

Reduzir o bloqueio das principais linhas de execução e da suspensão ininterrupta

Procure cada instância da linha de execução principal que entra em um estado bloqueado. O Perfetto e o Studio Profiler mostram isso com um indicador laranja na linha do tempo do estado da linha de execução. Identifique as operações, verifique se elas são esperadas ou podem ser evitadas e otimize o que for necessário.

A suspensão que pode ser interrompida e está relacionada a pedidos de veiculação pode ser uma boa oportunidade de melhoria. Outros processos que fazem pedido de veiculação, mesmo que não sejam apps não relacionados, podem lidar com o pedido que o app principal está fazendo.

Melhorar o tempo de inicialização

Depois de identificar uma oportunidade de otimização, teste algumas possíveis soluções para melhorar os tempos de inicialização:

  • Carregue conteúdo de maneira lenta e assíncrona para acelerar o TTID.
  • Minimize as funções de chamada que fazem chamadas de binder. Se elas forem inevitáveis, confira se você está otimizando essas chamadas armazenando valores em cache em vez de repeti-las ou de mover o trabalho sem bloqueio para linhas de execução em segundo plano.
  • Para que a inicialização do app apareça mais rapidamente, mostre ao usuário algo que precise do mínimo de renderização o mais rápido possível até que o restante da tela termine de carregar.
  • Crie e adicione um perfil de inicialização ao app.
  • Use a biblioteca App Startup do Jetpack para simplificar a inicialização dos componentes durante a inicialização do app.

Analisar o desempenho da interface

A inicialização do app inclui uma tela de apresentação e o tempo de carregamento da página inicial. Se quiser otimizar a inicialização do app, você poderá inspecionar os rastreamentos até entender o tempo necessário para que a interface seja renderizada.

Limitar o trabalho na inicialização

O carregamento de alguns frames pode levar mais tempo do que outros. Essas exibições são consideradas pesadas para o app.

Para otimizar a inicialização, siga estas etapas:

  • Priorize transmissões de layout lentas para fazer melhorias.
  • Investigue cada aviso do Perfetto e os alertas do Systrace adicionando eventos de rastreamento personalizados para reduzir atrasos e exibições pesadas.

Medir dados de frame

Há várias maneiras de medir dados de frame. Estes são os cinco principais métodos de coleta:

  • Coleta local usando dumpsys gfxinfo: nem todos os frames observados nos dados do dumpsys são responsáveis pela renderização lenta do app ou têm um impacto para os usuários finais. No entanto, essa é uma boa medida em diferentes ciclos de lançamento para entender a tendência geral de desempenho. Para saber mais sobre como usar gfxinfo e framestats para integrar a medição de desempenho da interface às suas práticas de teste, consulte Conceitos básicos para testar apps Android.
  • Coleta de campos usando JankStats: colete tempos de renderização de frames de partes específicas do app com a biblioteca JankStats para gravar e analisar o dados.
  • Em testes que usam Macrobenchmark (Perfetto em segundo plano).
  • Perfetto (em inglês) FrameTimeline: No Android 12 (nível 31 da API), é possível coletar a linha do tempo de frames métricas de um rastro do Perfetto para descobrir qual trabalho está causando o descarte do frame. Essa pode ser a primeira etapa para diagnosticar o motivo do descarte.
  • Android Studio Profiler para detecção de instabilidade.

Verificar o tempo de carregamento da atividade principal

A atividade principal do app pode conter uma grande quantidade de informações carregadas de várias fontes. Confira o layout da Activity inicial e observe o método Choreographer.onDraw da atividade inicial.

  • Use reportFullyDrawn para informar ao sistema que o app foi completamente renderizado para fins de otimização.
  • Meça atividades e inicializações de apps usando StartupTimingMetric com a biblioteca Macrobenchmark.
  • Observe os descartes de frames.
  • Identifique layouts que demoram para serem renderizados ou medidos.
  • Identifique os recursos que estão demorando muito para carregar.
  • Identifique layouts desnecessários que são inflados durante a inicialização.

Considere estas possíveis soluções para otimizar o tempo de carregamento da atividade principal:

  • Deixe o layout inicial o mais básico possível. Para saber mais, consulte Otimizar hierarquias de layout.
  • Adicione pontos de rastreamento personalizados para fornecer mais informações sobre frames descartados e layouts complexos.
  • Minimize o número e o tamanho dos recursos de bitmap carregados durante a inicialização.
  • Use ViewStub quando os layouts não ficarem imediatamente VISIBLE (visíveis). A ViewStub é uma visualização invisível de tamanho zero que pode ser usada para inflar lentamente recursos de layout no momento da execução. Para saber mais, consulte ViewStub.

    Se você estiver usando o Jetpack Compose, poderá ter um comportamento semelhante ao ViewStub usando o estado para adiar o carregamento de alguns componentes:

    var shouldLoad by remember {mutableStateOf(false)}
    
    if (shouldLoad) {
     MyComposable()
    }
    

    Carregue os elementos combináveis dentro do bloco condicional modificando shouldLoad:

    LaunchedEffect(Unit) {
     shouldLoad = true
    }
    

    Isso aciona uma recomposição que inclui o código dentro do bloco condicional no primeiro snippet.

.