Os usuários esperam que os apps sejam responsivos e carreguem rapidamente. 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 o TTFD é o tempo que leva para o app se tornar totalmente interativo. Ambos são igualmente importantes, já que o TTID permite que o usuário saiba que o app está sendo carregado e o TTFD informa quando o app pode ser usado. Se um deles for muito longo, o usuário poderá sair do app antes mesmo de ele ser totalmente carregado.
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:
- Carregar e iniciar o app.
- Mostrar uma janela de inicialização em branco para o app imediatamente após o início.
- Criar o processo do app.
Assim que é criado pelo sistema, o processo do app fica responsável pelas próximas etapas:
- Criar o objeto de app.
- Iniciar a linha de execução principal.
- Criar a atividade principal.
- Inflar visualizações.
- Fazer o layout da tela.
- 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.
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. Em seguida, o app gera
a linha de execução principal (também conhecida como linha de execução de interface) e a encarrega 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:
- Inicialização de valores.
- Chamada de construtores.
- 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,
já que 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.
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:
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.
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.
Fixe a linha "Android App Startups" clicando no ícone de fixação, que fica visível ao manter o ponteiro sobre a linha.
Role até a linha com o app em questão. Para abri-la, clique na primeira célula.
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.
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 inspecionar e melhorar a inicialização
Para diagnosticar adequadamente a performance 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) e tempo para exibição total (TTFD) 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 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 demora demais para inicializar.
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 conferir informações sobre como o Google Play coleta dados do Android vitals, consulte a documentação do Play Console.
Tempo para exibição inicial
O tempo para exibição inicial (TTID) é o tempo necessário para mostrar o primeiro frame
da interface do app. Essa métrica 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. Manter
o TTID do app baixo ajuda a melhorar a experiência do usuário, permitindo que ele
inicie o app rapidamente. O TTID é informado automaticamente para cada app pelo framework do
Android. Ao otimizar a inicialização do app, recomendamos implementar
reportFullyDrawn
para receber informações até o TTFD.
O TTID é medido como um valor de tempo que representa o tempo total decorrido, incluindo a sequência de eventos abaixo:
- Início do processo
- Inicialização dos objetos
- Criação e inicialização da atividade
- Inflação do layout
- Exibição do app pela primeira vez
Extrair o TTID
Para encontrar o TTID, pesquise na ferramenta de linha de comando Logcat uma linha de saída
com um valor chamado Displayed
. Esse valor é o TTID e é semelhante
ao exemplo abaixo, em que o TTID é de 3s534ms:
ActivityManager: Displayed com.android.myexample/.StartupTiming: +3s534ms
Para encontrar o TTID no Android Studio, desative os filtros na visualização do Logcat no
menu suspenso de filtros e encontre o tempo de Displayed
, conforme mostrado na Figura 5.
A desativação dos filtros é necessária porque o servidor do sistema, não o app
em si, atende a esse registro.
A métrica Displayed
na saída do Logcat não captura necessariamente o
tempo até que todos os recursos sejam carregados e mostrados. 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.
A linha Displayed
na saída do Logcat pode conter 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ó é mostrada quando há
uma diferença entre o tempo de inicialização da atividade individual e o total.
Recomendamos o uso do Logcat no Android Studio. No entanto, se você não estiver usando o Android
Studio, poderá medir o TTID executando o app com o comando
do gerenciador de atividades do shell do adb
. Confira 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
O tempo para exibição total (TTFD) é o tempo que leva para um app se tornar interativo para o usuário. Ele é informado como o tempo necessário para mostrar o primeiro frame da interface do app, bem como o conteúdo que é carregado de forma assíncrona após a exibição do frame inicial. Em geral, esse é o conteúdo principal carregado pela rede ou pelo disco, conforme informado pelo app. Em outras palavras, o TTFD inclui o TTID, bem como o tempo necessário para que o app possa ser usado. Manter o TTFD baixo ajuda a melhorar a experiência do usuário, permitindo uma interação rápida.
O sistema determina o TTID quando Choreographer
chama o método
onDraw()
da atividade e quando sabe que o chamado ocorreu pela primeira vez.
No entanto, o sistema não sabe quando determinar o TTFD, já que cada app
se comporta de maneira diferente. Para determinar o TTFD, o app precisa indicar ao sistema
o momento em que atinge o estado de exibição total.
Extrair o TTFD
Para encontrar o TTFD, indique o estado de exibição total chamando o método
reportFullyDrawn()
da ComponentActivity
. O
método reportFullyDrawn
informa quando o app está totalmente renderizado e em um estado
utilizável. O TTFD é o tempo decorrido entre o momento em que o sistema recebe a intent de inicialização
do app e o momento em que reportFullyDrawn()
é chamado. Se você não chamar
reportFullyDrawn()
, nenhum valor de TTFD será informado.
Para medir o TTFD, chame reportFullyDrawn()
depois de mostrar completamente a interface e
todos os dados. Não chame reportFullyDrawn()
antes que a janela da primeira
atividade seja mostrada pela primeira vez, conforme medido pelo sistema, porque ele
informa o tempo que o sistema mediu. Em outras palavras, se você chamar
reportFullyDrawn()
antes que o sistema detecte o TTID, ele vai informar que o
TTID e o TTFD têm o mesmo valor, e esse valor é o do TTID.
Quando você usa reportFullyDrawn()
, o Logcat mostra uma saída como o exemplo
abaixo, em que o TTFD é de 1s54ms:
system_process I/ActivityManager: Fully drawn {package}/.MainActivity: +1s54ms
A saída do Logcat pode incluir um tempo total
, conforme discutido em Tempo para
exibição inicial.
Se os tempos de exibição estiverem mais lentos do que o esperado, tente identificar os gargalos no processo de inicialização.
Você pode usar reportFullyDrawn()
para indicar o estado de exibição total em casos básicos,
quando você sabe que o estado de exibição total foi alcançado. No entanto, nos casos
em que as linhas de execução em segundo plano precisam concluir o trabalho antes que o estado de
exibição total seja alcançado, é necessário atrasar reportFullyDrawn()
para uma medição de
TTFD mais precisa. Para aprender a atrasar reportFullyDrawn()
, consulte a seção
abaixo.
Melhorar a precisão da marcação do tempo de inicialização
Se o app estiver executando o carregamento lento e a tela inicial não incluir
todos os recursos, como quando o app estiver buscando imagens da rede,
atrase a chamada de reportFullyDrawn
até que o app se torne
usável. Assim, você pode incluir o preenchimento da lista como parte do tempo de
comparação.
Por exemplo, se a interface contém uma lista dinâmica, como RecyclerView
ou uma lista lenta, talvez ela seja preenchida por uma tarefa em segundo plano concluída depois
que a lista for renderizada e, portanto, depois que a interface estiver marcada como totalmente renderizada.
Nesses casos, o preenchimento da lista não é incluído na comparação.
Para incluir o preenchimento da lista como parte da marcação do tempo de comparação, extraia o
FullyDrawnReporter
usando getFullyDrawnReporter()
e adicione um
informante no código do app. Libere o informante depois que a tarefa em segundo plano
terminar de preencher a lista.
O FullyDrawnReporter
não vai chamar o método reportFullyDrawn()
até que todos os
informantes adicionados sejam liberados. Ao adicionar um informante até que o processo em segundo plano
seja concluído, as marcações de tempo também vão incluir a quantidade de tempo necessária para preencher a
lista nos dados de marcação de tempo de inicialização. Isso não muda o comportamento do app para o
usuário, mas permite que os dados da marcação de tempo incluam o tempo necessário para preencher
a lista. O reportFullyDrawn()
não é chamado até que todas as tarefas sejam
concluídas, independente da ordem.
O exemplo abaixo mostra como é possível executar várias tarefas em segundo plano simultaneamente, cada uma registrando o próprio informante:
Kotlin
class MainActivity : ComponentActivity() { sealed interface ActivityState { data object LOADING : ActivityState data object LOADED : ActivityState } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { var activityState by remember { mutableStateOf(ActivityState.LOADING as ActivityState) } fullyDrawnReporter.addOnReportDrawnListener { activityState = ActivityState.LOADED } ReportFullyDrawnTheme { when(activityState) { is ActivityState.LOADING -> { // Display the loading UI. } is ActivityState.LOADED -> { // Display the full UI. } } } SideEffect { lifecycleScope.launch(Dispatchers.IO) { fullyDrawnReporter.addReporter() // Perform the background operation. fullyDrawnReporter.removeReporter() } lifecycleScope.launch(Dispatchers.IO) { fullyDrawnReporter.addReporter() // Perform the background operation. fullyDrawnReporter.removeReporter() } } } } }
Java
public class MainActivity extends ComponentActivity { private FullyDrawnReporter fullyDrawnReporter; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); fullyDrawnReporter = getFullyDrawnReporter(); fullyDrawnReporter.addOnReportDrawnListener(() -> { // Trigger the UI update. return Unit.INSTANCE; }); new Thread(new Runnable() { @Override public void run() { fullyDrawnReporter.addReporter(); // Do the background work. fullyDrawnReporter.removeReporter(); } }).start(); new Thread(new Runnable() { @Override public void run() { fullyDrawnReporter.addReporter(); // Do the background work. fullyDrawnReporter.removeReporter(); } }).start(); } }
Se o app usa o Jetpack Compose, você pode usar as APIs abaixo para indicar o estado de exibição total:
ReportDrawn
: indica que o elemento combinável está pronto para interação.ReportDrawnWhen
: usa um predicado, comolist.count > 0
, para indicar quando o elemento combinável está pronto para interação.ReportDrawnAfter
: usa um método de suspensão que, quando concluído, indica que o elemento combinável está pronto para interação.
Identificar gargalos
Para procurar gargalos, você pode usar o CPU Profiler do Android Studio. Para saber mais, consulte Inspecionar atividades de CPU com o CPU Profiler.
Você também pode conferir mais detalhes sobre possíveis gargalos usando o rastreamento inline nos
métodos onCreate()
dos seus apps e atividades. Para saber mais 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.
- Como 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 marcador de posição para sub-hierarquias que o app pode inflar em um momento mais adequado.
- 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 desta forma:
- 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ê note 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 (nível 30 da API) ou versões anteriores:
- O atributo de tema
windowDisablePreview
para desativar a tela em branco inicial mostrada pelo sistema durante a inicialização. - Uso de um
Activity
dedicado.
No Android 12 e versões mais recentes, é necessário migrar para a API SplashScreen
.
Ela possibilita um tempo de inicialização mais rápido e também um ajuste de tela de apresentação destas
formas:
- Defina um tema para mudar a aparência da tela de apresentação.
- Controle por quanto tempo a tela de apresentação é mostrada com
windowSplashScreenAnimationDuration
. - Personalize a animação da tela de apresentação e processe corretamente a animação para dispensá-la.
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 mostrar a
tela de apresentação em todas as versões do Android.
Para saber mais, consulte o guia para migrar a tela de apresentação.
Recomendados para você
- Observação: o texto do link aparece quando o JavaScript está desativado.
- Renderização lenta
- Capturar métricas de Macrobenchmark
- Criar perfis de referência{:#creating-profile-rules}