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 total (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.
- Inicialize a interface.
- Desenhe a interface na tela.
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.
Para mais informações sobre as fases de layout no Compose, consulte Fases do Jetpack Compose.
Problemas de desempenho podem surgir durante a criação do app e da atividade de host.
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 a
atividade de host do app.
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. Em
um app do Jetpack Compose, a sobrecarga do método onCreate geralmente vem da
composição inicial. Isso acontece quando o app chama setContent e
invoca os elementos combináveis de nível superior. Uma hierarquia de interface profunda ou complexa ou
elementos combináveis que realizam cálculos pesados na linha de execução principal podem aumentar
esse tempo de composição.
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 a atividade do host do app em primeiro plano. Se toda a interface do app ainda estiver na memória, ele poderá evitar a repetição da inicialização de objetos, da interface e da renderização.
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.
Figura 2. Fração da métrica derivada para inicialização de apps Android no Perfetto. 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.
Figura 3. A fração da métrica derivada para inicialização de apps Android ao lado da linha de execução principal do app. 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.
Ao analisar um aplicativo Jetpack Compose, use o
rastreamento de composição para ver detalhes granulares sobre o desempenho da sua interface.
Preste atenção especial às seções de rastreamento na linha de execução principal que começam com
Choreographer#doFrame. Procure intervalos como Compose:recompose, Compose:layout e Compose:draw. Elas mostram quanto tempo o app gasta compondo, medindo, posicionando e desenhando componentes específicos.
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 do host.
- Initializing the UI.
- 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 4.
A desativação dos filtros é necessária porque o servidor do sistema, não o app
em si, atende a esse registro.
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 mostrados. Ela exclui
recursos que não são referenciados na composição inicial (como dados ou imagens carregados
de forma assíncrona) ou recursos 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.
Para mais informações sobre recursos, consulte Recursos no Compose.
Às vezes, a linha Displayed na saída do Logcat contém um outro campo para o
tempo total, como no exemplo a seguir:
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, geralmente a atividade do host. A medição do tempo total começa no início do processo do app e pode incluir outra atividade que é iniciada primeiro, mas 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. 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
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.
Embora o sistema possa determinar o TTID quando a janela do host renderiza o frame inicial, ele não pode determinar automaticamente o TTFD. Como os apps geralmente carregam o conteúdo principal de forma assíncrona, o sistema não sabe quando o app está totalmente utilizável para o usuário. Para determinar o TTFD, o app precisa indicar ao sistema quando 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 LazyColumn ou
LazyRow, 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 fullyDrawnReporter 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, mas não liberá-lo até que
o processo em segundo plano seja concluído, você garante que os dados de marcação de tempo de inicialização
incluam o tempo de preenchimento da lista, sem mudar o comportamento do app para o
usuário. O reportFullyDrawn não é chamado até que todas as tarefas sejam
concluídas, independente da ordem.
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.
O exemplo abaixo mostra como é possível executar várias tarefas em segundo plano simultaneamente, cada uma registrando o próprio informante:
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 {
fullyDrawnReporter.addReporter()
lifecycleScope.launch(Dispatchers.IO) {
// Perform the background operation.
fullyDrawnReporter.removeReporter()
}
fullyDrawnReporter.addReporter()
lifecycleScope.launch(Dispatchers.IO) {
// Perform the background operation.
fullyDrawnReporter.removeReporter()
}
}
}
}
}
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
onCreateinicial 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:
- Inicializar uma interface grande ou complexa.
- Inicialização pesada em elementos combináveis.
- Bloquear desenho de tela em disco ou E/S de rede.
- Carregar e decodificar bitmaps.
- Como fazer varredura de objetos
VectorDrawable. - Inicialização de outros subsistemas da atividade do host do app.
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
onCreateinicial 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 interface, mais tempo o app levará para inicializá-la.
Considere as seguintes etapas ao resolver esse problema:
- Nivele sua hierarquia de UI reduzindo elementos combináveis redundantes ou aninhados.
- Evite composição e recomposição desnecessárias durante o lançamento.
- Adie a composição da interface não essencial.
- 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.
- Carregue e mostre a interface com dados de marcador de posição primeiro e, depois, atualize as propriedades visuais que dependem de bitmaps e outros recursos.
Para mais informações sobre recomposição, consulte Recomposição e Receber contagens de recomposição.
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
windowDisablePreviewpara desativar a tela em branco inicial mostrada pelo sistema durante a inicialização. - Uso de um
Activitydedicado.
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}