Tempo de inicialização do app

Os usuários esperam que os apps sejam responsivos e rápidos para carregar. Um app com tempo de inicialização lento não atende a essa expectativa e pode decepcionar os usuários. Esse tipo de experiência ruim pode fazer com que o usuário dê uma classificação negativa ao seu app na Play Store ou até mesmo desista de usá-lo.

Esta página fornece informações para otimizar o tempo de inicialização do seu app, incluindo uma visão geral das partes internas do processo de lançamento, como criar um perfil do desempenho de inicialização, alguns problemas comuns e dicas para resolvê-los.

Entender os diferentes estados de inicialização do app

A inicialização do app pode ocorrer em um destes três estados: inicialização a frio, inicialização com estado salvo ou inicialização a quente. Cada estado afeta o tempo que leva para o app ficar visível para o usuário. Em uma inicialização a frio, o app é iniciado do zero. Nos outros estados, o sistema precisa levar o app que está em execução em segundo plano para o primeiro plano.

Recomendamos que você sempre otimize presumindo que se trate de uma inicialização a frio. Ao fazer isso, o desempenho de inicializações a quente e com estado salvo também pode melhorar.

Para otimizar seu app para uma inicialização rápida, vale a pena entender o que está acontecendo nos níveis do sistema e do app e como eles interagem em cada um desses estados.

Duas métricas importantes para determinar a inicialização do app são o tempo para exibição inicial (TTID, na sigla em inglês) e o tempo para exibição completa (TTFD, na sigla em inglês). O TTID é o tempo necessário para mostrar o primeiro frame, e TTFD é o tempo necessário para o app se tornar totalmente interativo. Ambos são igualmente importantes, porque o TTID permite que o usuário saiba que o app está sendo carregado e o TTFD informa quando o app pode ser utilizado. Se um deles for muito longo, o usuário poderá sair do app antes mesmo de ele ser totalmente carregado.

Para receber valores precisos para o TTFD, indique quando o app atingir um estado totalmente desenhado para ajudar a garantir que tempos precisos sejam medidos. Para saber como fazer isso, consulte Como melhorar a precisão do tempo de inicialização.

Inicialização a frio

Uma inicialização a frio refere-se a um app que é inicializado do zero. Isso significa que, até esse início, o processo do sistema cria o processo do app. Inicializações a frio ocorrem em casos em que o app é iniciado pela primeira vez desde que o dispositivo foi inicializado ou quando o sistema o eliminou.

Esse tipo de inicialização representa o maior desafio para minimizar o tempo de inicialização, porque o sistema e o app têm mais trabalho a fazer do que nos outros estados de inicialização.

No início de uma inicialização a frio, o sistema tem estas três tarefas:

  1. Carregar e iniciar o app.
  2. Mostrar uma janela de inicialização em branco para o app imediatamente após o início.
  3. Criar o processo do app.

Assim que é criado pelo sistema, o processo do app fica responsável pelas próximas etapas:

  1. Criar o objeto de app.
  2. Iniciar a linha de execução principal.
  3. Criar a atividade principal.
  4. Inflar visualizações.
  5. Fazer o layout da tela.
  6. Executar o desenho inicial.

Quando o processo do app conclui o primeiro desenho, o processo do sistema substitui a janela de segundo plano exibida pela atividade principal. Nesse ponto, o usuário pode começar a usar o app.

A figura 1 mostra como os processos do sistema e do app transferem o trabalho entre si.

Figura 1. Uma representação visual das partes importantes da inicialização a frio de um app.

Problemas de desempenho podem surgir durante a criação do app e a criação da atividade.

Criação de apps

Quando o app é iniciado, a janela inicial em branco permanece na tela até o sistema concluir o desenho do app pela primeira vez. Nesse momento, o processo do sistema troca a janela inicial do app, permitindo que o usuário interaja com ele.

Se você substituir Application.onCreate() no seu próprio app, o sistema vai invocar o método onCreate() no objeto do app. Depois, o app vai gerar a linha de execução principal (também conhecida como linha de execução da interface) e a encarregar de criar sua atividade principal.

A partir desse ponto, os processos no nível do sistema e do app prosseguem de acordo com os estágios do ciclo de vida do app.

Criação da atividade

Depois que o processo do app cria sua atividade, ela executa as seguintes operações:

  1. Inicialização de valores.
  2. Chamada de construtores.
  3. Chama o método de callback, por exemplo, Activity.onCreate(), apropriado para o estado atual do ciclo de vida da atividade.

Normalmente, o método onCreate() tem o maior impacto no tempo de carregamento, porque executa o trabalho com maior overhead: carrega e infla visualizações e inicializa os objetos necessários para a execução da atividade.

Inicialização com estado salvo

Uma inicialização com estado salvo abrange um subconjunto das operações que ocorrem durante uma inicialização a frio. Ao mesmo tempo, ela representa mais sobrecarga do que uma inicialização a quente. Há muitos estados em potencial que podem ser considerados inicializações com estado salvo, como:

  • O usuário sai do app e depois o reinicializa. O processo pode continuar em execução, mas o app precisa recriar a atividade do zero usando uma chamada para onCreate().

  • O sistema elimina seu app da memória e, em seguida, o usuário o reinicializa. O processo e a atividade precisam ser reiniciados, mas a tarefa pode se beneficiar um pouco do pacote de estado da instância salvo, transmitido para onCreate().

Inicialização a quente

Uma inicialização a quente do app tem uma sobrecarga menor do que uma inicialização a frio. Em uma inicialização a quente, o sistema coloca sua atividade em primeiro plano. Se todas as atividades do app ainda estiverem na memória, ele poderá evitar a repetição da inicialização, a inflação de layouts e a renderização de objetos.

No entanto, se alguma memória for limpa em resposta a eventos de corte de memória, como onTrimMemory(), esses objetos precisarão ser recriados em resposta ao evento de inicialização a quente.

Uma inicialização a quente exibe o mesmo comportamento que o do cenário de uma inicialização a frio: o processo do sistema mostra uma tela em branco até que o app termine de renderizar a atividade.

Figura 2. Um diagrama com os vários estados de inicialização e os respectivos processos. Cada estado começa no primeiro frame desenhado.

Como identificar a inicialização do app no Perfetto

Para depurar problemas de inicialização do app, determine exatamente o que está incluído na fase de inicialização. Para identificar toda a fase de inicialização do app no Perfetto, siga estas etapas:

  1. No Perfetto, encontre a linha com a métrica derivada para inicialização de apps Android. Caso ela não esteja disponível, tente capturar um rastro usando o app de rastreamento do sistema no dispositivo.

    Figura 3. Fração da métrica derivada para inicialização de apps Android no Perfetto.
  2. Clique na fração associada e pressione m para selecioná-la. Os colchetes aparecem ao redor da fração e indicam o tempo transcorrido. A duração também é mostrada na guia Seleção atual.

  3. Fixe a linha "Android App Startups" clicando no ícone de fixação, que fica visível ao manter o ponteiro sobre a linha.

  4. Role para baixo até a linha com o app em questão. Para abri-la, clique na primeira célula.

  5. Aumente o zoom da linha de execução principal, geralmente localizado na parte de cima, pressionando w. Pressione s, a, d para diminuir o zoom, mover para a esquerda e mover para a direita, respectivamente.

    Figura 4. Fração da métrica derivada para inicialização de apps Android ao lado da linha de execução principal do app.
  6. A fração da métrica derivada facilita a visualização exata do que está incluído na inicialização do app para que você possa continuar a depuração com mais detalhes.

Usar métricas para detectar e diagnosticar problemas

Para diagnosticar adequadamente o desempenho do tempo de inicialização, é possível rastrear métricas que mostram o tempo necessário para que o app seja iniciado. O Android oferece várias maneiras de mostrar que o app está com um problema e ajuda a diagnosticá-lo. O recurso "Android vitals" pode alertar que um problema está ocorrendo, e as ferramentas de diagnóstico podem ajudar a diagnosticar o problema.

Benefícios do uso de métricas de inicialização

O Android usa as métricas de tempo para exibição inicial (TTID, na sigla em inglês) e tempo para exibição total (TTFD, na sigla em inglês) para otimizar as inicializações de app a frio e com estado salvo. O Android Runtime (ART) usa os dados dessas métricas para pré-compilar de forma eficiente
o código e assim otimizar futuras inicializações.

Inicializações mais rápidas levam a uma interação mais consistente com o app, o que reduz as instâncias de saída antecipada, a reinicialização da instância ou a saída para outro app.

Android vitals

O Android vitals pode ajudar a melhorar o desempenho do seu app, mostrando alertas no Play Console quando o app exceder os tempos de execução.

O Android vitals considera os seguintes tempos de inicialização do app excessivos:

  • a inicialização a frio do app leva 5 segundos ou mais;
  • a inicialização com estado salvo leva 2 segundos ou mais;
  • a inicialização a quente leva 1,5 segundo ou mais.

O Android vitals usa a métrica Tempo para exibição inicial (TTID). Para informações sobre como o Google Play coleta dados do Android vitals, consulte a documentação do Play Console.

Tempo para exibição inicial

A métrica de tempo para exibição inicial (TTID, na sigla em inglês) mede o tempo que um app leva para produzir o primeiro frame, incluindo a inicialização do processo durante uma inicialização a frio, a criação de atividades durante uma inicialização a frio ou com estado salvo e a exibição do primeiro frame.

Como recuperar o TTID

O Logcat inclui uma linha de saída que contém um valor chamado Displayed. Esse valor representa o tempo total decorrido entre o início do processo e o término do desenho da atividade correspondente na tela. O tempo decorrido abrange a seguinte sequência de eventos:

  • Início do processo.
  • Inicialização dos objetos.
  • Criação e inicialização da atividade.
  • Inflação do layout.
  • Exibição do seu app pela primeira vez.

A linha de registro informada é semelhante a este exemplo:

ActivityManager: Displayed com.android.myexample/.StartupTiming: +3s534ms

Se você estiver rastreando a saída do Logcat na linha de comando ou em um terminal, será fácil encontrar o tempo decorrido. Para encontrá-lo no Android Studio, desative os filtros na visualização do Logcat. A desativação dos filtros é necessária porque o servidor do sistema, não o app em si, atende a esse registro.

Depois de definir as configurações apropriadas, pesquise o termo correto para conferir as horas. A Figura 3 mostra como desativar filtros selecionando "No filters" na lista suspensa e, na segunda linha da saída da parte inferior, um exemplo de saída do Logcat do tempo Displayed.

Figura 5. Desativação de filtros e descoberta do valor de Displayed no Logcat.

A métrica Displayed na saída do Logcat não captura necessariamente o tempo até que todos os recursos sejam carregados e exibidos. Ela exclui recursos que não são referenciados no arquivo de layout ou que o app cria como parte da inicialização do objeto. Ela exclui esses recursos porque o carregamento deles é um processo inline e não bloqueia a exibição inicial do app.

Às vezes, a linha Displayed na saída do logcat contém um outro campo para o tempo total. Por exemplo:

ActivityManager: Displayed com.android.myexample/.StartupTiming: +3s534ms (total +1m22s643ms)

Nesse caso, a primeira medição de tempo é apenas da atividade que é desenhada pela primeira vez. A medição do tempo total começa no início do processo do app e pode incluir outra atividade que é iniciada primeiro, mas que não é exibida na tela. A medição de tempo total só é exibida quando há uma diferença entre tempo de inicialização da atividade individual e o total.

Você também pode medir o TTID executando seu app com o comando de Gerenciador de atividades do shell do adb. Veja um exemplo:

adb [-d|-e|-s <serialNumber>] shell am start -S -W
com.example.app/.MainActivity
-c android.intent.category.LAUNCHER
-a android.intent.action.MAIN

A métrica Displayed aparece na saída do logcat como antes. A janela de terminal mostra o seguinte:

Starting: Intent
Activity: com.example.app/.MainActivity
ThisTime: 2044
TotalTime: 2044
WaitTime: 2054
Complete

Os argumentos -c e -a são opcionais e permitem especificar <category> e <action>.

Tempo para exibição total

A métrica "Tempo para exibição total" (TTFD, na sigla em inglês) mede o tempo necessário para que o app produza o primeiro frame com conteúdo completo, incluindo o conteúdo carregado de forma assíncrona após o primeiro frame. Em geral, esse é o conteúdo da lista principal carregado pela rede, conforme relatado pelo app.

Como recuperar o TTFD

Você pode usar o método reportFullyDrawn() para medir o tempo decorrido entre a inicialização do app e a exibição completa de todos os recursos e das hierarquias de visualização. Isso pode ser valioso nos casos em que um app executa carregamento lento. No carregamento lento, um app não bloqueia o desenho inicial da janela, mas carrega recursos de forma assíncrona e atualiza a hierarquia de visualização.

Devido ao carregamento lento, se a exibição inicial de um app não incluir todos os recursos, considere o carregamento e a exibição completos de todos os recursos e visualizações como uma métrica separada. Por exemplo, sua interface pode estar totalmente carregada, com algum texto desenhado, mas ainda não mostra imagens que o app precisa buscar na rede.

Melhorar a precisão do tempo de inicialização explica como usar FullyDrawnReporter para atrasar chamadas reportFullyDrawn até que seu app se torne interativo para o usuário.

Quando você usa essa API, o valor exibido pelo Logcat é o tempo decorrido entre a criação do objeto do app e o momento em que reportFullyDrawn() é chamado. Veja um exemplo da saída do logcat:

system_process I/ActivityManager: Fully drawn {package}/.MainActivity: +1s54ms

Às vezes, a saída do Logcat inclui um tempo total, conforme discutido em Tempo para exibição inicial.

Se você descobrir que os tempos de exibição estão mais lentos do que o esperado, tente identificar os gargalos no processo de inicialização.

Identificar gargalos

Para procurar gargalos, você pode usar o CPU Profiler do Android Studio. Para mais informações, consulte Inspecionar atividades de CPU com o CPU Profiler.

Você também pode saber mais sobre possíveis gargalos usando o rastreamento inline nos métodos onCreate() dos seus apps e atividades. Para mais informações sobre o rastreamento inline, consulte a documentação das funções Trace e a visão geral do rastreamento do sistema.

Resolver problemas comuns

Esta seção discute vários problemas que geralmente afetam o desempenho da inicialização dos apps. Esses problemas referem-se principalmente à inicialização de apps e objetos de atividade, bem como ao carregamento de telas.

Inicialização de apps pesados

O desempenho de inicialização pode ser afetado quando seu código modifica o objeto Application e executa um trabalho pesado ou lógica complexa ao inicializar esse objeto. Seu app poderá perder tempo durante a inicialização se as subclasses Application realizarem inicializações que ainda não precisam ser feitas.

Algumas inicializações podem ser completamente desnecessárias, como ao inicializar informações de estado para a atividade principal quando o app é realmente iniciado em resposta a uma intent. Com uma intent, o app usa apenas um subconjunto dos dados de estado inicializados anteriormente.

Outros desafios durante a inicialização do app incluem eventos de coleta de lixo impactantes ou numerosos, ou E/S de disco que aconteçam simultaneamente com a inicialização, bloqueando ainda mais o processo de inicialização. A coleta de lixo é especialmente uma consideração no tempo de execução do Dalvik. O Android Runtime (ART) executa a coleta de lixo simultaneamente, minimizando o impacto dessa operação.

Diagnosticar o problema

Você pode usar o rastreamento de métodos ou in-line para tentar diagnosticar o problema.

Rastreamento de métodos

A execução do CPU Profiler revela que o método callApplicationOnCreate() finalmente chama seu método com.example.customApplication.onCreate. Se a ferramenta mostrar que esses métodos estão demorando muito para terminar a execução, continue investigando para descobrir qual trabalho está ocorrendo.

Rastreamento in-line

Use o rastreamento inline para investigar as causas prováveis, incluindo as seguintes:

  • A função onCreate() inicial do app.
  • Quaisquer objetos singleton que seu app inicializa.
  • E/S de disco, desserialização ou loop apertado que possa estar ocorrendo durante o gargalo.

Soluções para o problema

Se o problema está nas inicializações desnecessárias ou na E/S de disco, a solução é a inicialização lenta. Em outras palavras, inicialize apenas os objetos imediatamente necessários. Em vez de criar objetos estáticos globais, mude para um padrão Singleton, em que o app inicializa objetos apenas na primeira vez que eles são necessários.

Além disso, você pode usar um framework de injeção de dependências como Hilt, que cria objetos e dependências quando injetados pela primeira vez.

Se o app usa provedores de conteúdo para iniciar componentes de apps na inicialização, considere usar a biblioteca App Startup.

Inicialização de atividades pesadas

A criação de atividades geralmente envolve muito trabalho com sobrecarga. Muitas vezes, há oportunidades de otimizar esse trabalho para melhorar o desempenho. Esses problemas comuns incluem o seguinte:

  • Inflar layouts grandes ou complexos.
  • Bloquear desenho de tela em disco ou E/S de rede.
  • Carregar e decodificar bitmaps.
  • Fazer varredura de objetos VectorDrawable.
  • Inicializar outros subsistemas da atividade.

Diagnosticar o problema

Nesse caso também, o rastreamento de métodos e inline podem ser úteis.

Rastreamento de métodos

Ao usar o CPU Profiler, preste atenção aos construtores de subclasse Application e aos métodos com.example.customApplication.onCreate().

Se a ferramenta mostrar que esses métodos estão demorando muito para terminar a execução, continue investigando para descobrir qual trabalho está ocorrendo.

Rastreamento in-line

Use o rastreamento inline para investigar as causas prováveis, incluindo as seguintes:

  • A função onCreate() inicial do app.
  • Qualquer objeto singleton global inicializado.
  • E/S de disco, desserialização ou loop apertado que possa estar ocorrendo durante o gargalo.

Soluções para o problema

Há muitos gargalos em potencial, mas dois problemas e soluções comuns são os seguintes:

  • Quanto maior for sua hierarquia de visualizações, mais tempo o app levará para inflá-la. Você pode executar duas etapas para solucionar esse problema:
    • Nivelar sua hierarquia de visualizações, reduzindo layouts redundantes ou aninhados.
    • Não infle partes da interface que não precisam estar visíveis durante a inicialização. Em vez disso, use um objeto ViewStub como um marcador para sub-hierarquias que o app pode inflar em um momento mais apropriado.
  • Colocar toda a inicialização de recursos na linha de execução principal também pode deixar a inicialização lenta. Você pode solucionar esse problema da seguinte maneira:
    • Mova toda a inicialização de recursos para outra linha de execução, para que o app possa inicializá-los lentamente.
    • Permita que o app carregue e mostre as visualizações e só depois atualize as propriedades visuais que dependem de bitmaps e outros recursos.

Telas de apresentação personalizadas

Talvez você veja um tempo extra durante a inicialização se tiver usado um dos seguintes métodos para implementar uma tela de apresentação personalizada no Android 11 (API de nível 30) ou versões anteriores:

  • Uso do atributo de tema windowDisablePreview para desativar a tela em branco inicial desenhada pelo sistema durante a inicialização.
  • Uso de um Activity dedicado.

A partir do Android 12, é necessário migrar para a API SplashScreen. Essa API possibilita um tempo de inicialização mais rápido e também um ajuste de tela de apresentação das seguintes maneiras:

Além disso, a biblioteca de compatibilidade faz o backport da API SplashScreen para ativar a compatibilidade com versões anteriores e criar uma aparência consistente para exibição da tela de apresentação em todas as versões do Android.

Para mais detalhes, consulte o guia Migrar a implementação de tela de apresentação.