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 lenta não atende a essa expectativa e pode ser decepcionante para os usuários. Esse tipo de experiência ruim pode fazer com que um usuário classifique negativamente seu app na Play Store ou até mesmo desista de usá-lo.

Este documento fornece informações para ajudar a otimizar o tempo de inicialização do seu app. Ele começa explicando os aspectos internos do processo de inicialização. Em seguida, ele mostra como analisar o desempenho da inicialização. Por fim, descreve alguns problemas comuns no tempo de inicialização e fornece algumas dicas sobre como resolvê-los.

Compreender os aspectos internos de inicialização do app

A inicialização de um app pode ocorrer em um de três estados: inicialização a frio, inicialização lenta com o app aberto em segundo plano ou inicialização a quente. Cada um desses estados 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 inicializações lentas com a app aberto em segundo plano 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.

Inicialização a frio

Uma inicialização a frio refere-se a um app que é inicializado do zero: o processo do sistema, até a inicialização, não criou o processo do app. A inicialização a frio acontece, por exemplo, quando o app é inicializado pela primeira vez depois de uma inicialização ou do app ser fechado pelo sistema. 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 três tarefas. São elas:

  1. Carregamento e inicialização do app.
  2. Exibição de uma janela de inicialização em branco para o app imediatamente após o início.
  3. Criação do processo do app.

Assim que o sistema cria o processo do app, esse processo é responsável pelas próximas etapas:

  1. Criação do objeto do app.
  2. Inicialização da linha de execução principal.
  3. Criação da atividade principal.
  4. Aumento das visualizações.
  5. Definição do layout da tela.
  6. Execução do desenho inicial.

Depois que o processo do app conclui o primeiro desenho, o processo do sistema substitui a janela de segundo plano exibida no momento 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 do aplicativo

Quando o aplicativo é iniciado, a janela inicial em branco permanece na tela até o sistema concluir o desenho do app pela primeira vez. Nesse ponto, o processo do sistema substitui a janela inicial do app, permitindo que o usuário comece a interagir com o app.

Se você sobrecarregou Application.onCreate() no seu app, o sistema invoca o método onCreate() no objeto do seu app. Depois, o app gera a linha de execução principal, também conhecida como "linha de execução da UI", 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:

  1. Inicialização de valores.
  2. Chamada de construtores.
  3. Chamada do método de callback adequado, como Activity.onCreate(), ao 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 a maior sobrecarga: carregando e inflando visualizações e inicializando os objetos necessários para a execução da atividade.

Inicialização a quente

Uma inicialização a quente do app é muito mais simples e com menos sobrecarga que uma inicialização a frio. Nesse tipo de inicialização, o sistema simplesmente leva sua atividade para o primeiro plano. Se todas as atividades do seu app ainda estiverem na memória, o app poderá evitar a repetição da inicialização, a inflação do layout e renderização de objetos.

No entanto, se alguma memória tiver sido 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 exibe uma tela em branco até que o app termine de renderizar a atividade.

Inicialização lenta com o app aberto em segundo plano

Uma inicialização lenta com o app aberto em segundo plano inclui um subconjunto das operações que acontecem durante uma inicialização a frio. Ao mesmo tempo, ela representa mais sobrecarga do que uma inicialização lenta. Há muitos estados em potencial que podem ser considerados inicialização lenta com o app aberto em segundo plano. Por exemplo:

  • O usuário sai do app e depois o reinicializa. O processo pode continuar a ser executado, mas o app precisa recriar a atividade desde o início por meio de 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 do pacote de estados da instância salva transmitido para onCreate().

Detectar e diagnosticar problemas

O Android oferece vários meios de informar que seu app está com um problema e ajudar a diagnosticá-lo. O recurso "Android vitals" pode alertar que o problema está ocorrendo, e as ferramentas de diagnóstico podem ajudar a diagnosticar o problema.

Android vitals

O "Android vitals" pode ajudar a melhorar o desempenho do seu app, alertando por meio do Play Console quando os tempos de execução do app são excessivos. O "Android vitals" considera os tempos de inicialização do app excessivos quando:

Uma sessão diária refere-se a um dia em que seu app foi usado.

O "Android vitals" não informa dados para inicializações a quente. Para ver informações sobre como o Google Play coleta dados do "Android vitals", consulte a documentação do Play Console.

Como diagnosticar tempos de inicialização lenta

Para diagnosticar adequadamente o desempenho dos tempos de inicialização, você pode rastrear métricas que mostram o tempo necessário para que o app seja iniciado.

Tempo para exibição inicial

No Android 4.4 (API de nível 19) e versões posteriores, o logcat inclui uma linha de saída com 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:

  1. Início do processo.
  2. Inicialização dos objetos.
  3. Criação e inicialização da atividade.
  4. Inflação do layout.
  5. Desenho do 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, é preciso desativar 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 adequadas, você pode pesquisar facilmente o termo correto para ver o tempo. A figura 2 mostra como desativar filtros e, na penúltima linha da saída, um exemplo da saída do logcat do tempo de Displayed.


Figura 2. Desativação de filtros e descoberta do valor de Displayed no logcat.

A métrica de Displayed na saída do logcat não captura necessariamente o tempo total 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 in-line e não bloqueia a exibição inicial do app.

Às vezes, a linha de Displayed na saída do logcat contém um campo adicional 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 foi 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 foi iniciada primeiro, mas não foi exibida na tela. A medição do tempo total é mostrada apenas quando há uma diferença entre o tempo de inicialização da atividade individual e o total.

Você também pode medir o tempo para a exibição inicial 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 do seu terminal também exibe 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 que você especifique <category> e <action> para o intent.

Tempo para exibição total

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.

Se, devido ao carregamento lento, 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 IU pode estar totalmente carregada, com algum texto desenhado, mas ainda não exibe imagens que o app precisa buscar na rede.

Para solucionar esse problema, você pode chamar reportFullyDrawn() manualmente para informar ao sistema que sua atividade foi concluída com o carregamento lento. Quando você usa esse método, o valor que o logcat exibe é o tempo decorrido entre a criação do objeto do aplicativo 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ê verificar que os tempos de exibição estão mais lentos do que você gostaria, tente identificar os gargalos no processo de inicialização.

Identificação de gargalos

Uma boa maneira de procurar gargalos é usando o CPU Profiler do Android Studio. Para mais informações, consulte Inspecionar atividades de CPU com o CPU Profiler.

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

Conhecer 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 da inicialização pode ser afetado quando seu código substitui o objeto Application e executa um trabalho pesado ou uma lógica complexa ao inicializar esse objeto. Seu app poderá perder tempo durante a inicialização se as subclasses de Application executarem inicializações que não precisam ser realizadas. Algumas inicializações podem ser completamente desnecessárias. Por exemplo, inicializar informações sobre o estado da atividade principal quando o app foi iniciado em resposta a um intent. Com um 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 tempo de execução de ART executa a coleta de lixo simultaneamente, minimizando o impacto dessa operação.

Diagnóstico do 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() chama o método com.example.customApplication.onCreate. Se a ferramenta mostrar que a execução desses métodos está demorando muito para terminar, você precisará investigar mais para descobrir qual trabalho está ocorrendo.

Rastreamento in-line

Use o rastreamento in-line para investigar causas prováveis, incluindo:

  • A função onCreate() inicial do seu 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 pede objetos de inicialização lenta: inicializando apenas aqueles que são imediatamente necessários. Por exemplo, em vez de criar objetos estáticos globais, mude para um padrão de singleton, em que o app inicializa objetos apenas na primeira vez que os acessa. Além disso, você pode usar um framework de injeção de dependências como Dagger, que cria objetos e dependências quando injetados pela primeira vez.

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. Tais problemas comuns incluem:

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

Diagnóstico do problema

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

Rastreamento de métodos

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

Se a ferramenta mostrar que esses métodos estão demorando muito para terminar a execução, você precisará explorar mais para ver qual trabalho está ocorrendo.

Rastreamento in-line

Use o rastreamento in-line para investigar causas prováveis, incluindo:

  • A função onCreate() inicial do seu 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 inflar partes da IU 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 adequado.
  • Se toda a inicialização de recursos estiver na linha de execução principal, isso também poderá tornar a inicialização lenta. Você pode solucionar esse problema da seguinte maneira:
    • Mova toda a inicialização de recursos para que o app possa executá-la lentamente em uma linha de execução diferente.
    • Permita que o app carregue e exiba suas visualizações e, depois, atualize as propriedades visuais dependentes de bitmaps e outros recursos.

Telas iniciais com tema

Você pode definir o tema da experiência de carregamento do seu app para que a tela de início do app tenha um tema consistente com o restante dele, e não com o tema do sistema. Isso pode ocultar uma inicialização lenta de atividades.

Uma maneira comum de implementar uma tela de inicialização com tema é usar o atributo de tema windowDisablePreview para desativar a tela em branco inicial que o processo do sistema desenha ao iniciar o app. No entanto, essa abordagem pode resultar em um tempo de inicialização mais longo do que o dos apps que não suprimem a janela de visualização. Além disso, ela força o usuário a esperar enquanto a atividade é iniciada, sem saber se o app está funcionando corretamente.

Diagnóstico do problema

Muitas vezes, você pode diagnosticar esse problema observando uma resposta lenta quando um usuário inicia seu app. Nesse caso, a tela pode parecer congelada ou ter parado de responder à entrada.

Soluções para o problema

Recomendamos que, em vez de desativar a janela de visualização, você siga os padrões comuns do Material Design. Você pode usar o atributo de tema windowBackground da atividade para fornecer um drawable personalizado simples para a atividade inicial.

Por exemplo, você pode criar um novo arquivo de drawables e fazer referência a ele no XML de layout e no arquivo de manifesto do app da seguinte maneira:

Arquivo XML de layout:

    <layer-list xmlns:android="http://schemas.android.com/apk/res/android" android:opacity="opaque">
      <!-- The background color, preferably the same as your normal theme -->
      <item android:drawable="@android:color/white"/>
      <!-- Your product logo - 144dp color version of your app icon -->
      <item>
        <bitmap
          android:src="@drawable/product_logo_144dp"
          android:gravity="center"/>
      </item>
    </layer-list>
    

Arquivo de manifesto:

    <activity ...
    android:theme="@style/AppTheme.Launcher" />
    

A maneira mais fácil de voltar ao tema normal é chamar setTheme(R.style.AppTheme) antes de chamar super.onCreate() e setContentView():

Kotlin

    class MyMainActivity : AppCompatActivity() {

        override fun onCreate(savedInstanceState: Bundle?) {
            // Make sure this is before calling super.onCreate
            setTheme(R.style.Theme_MyApp)
            super.onCreate(savedInstanceState)
            // ...
        }
    }
    

Java

    public class MyMainActivity extends AppCompatActivity {
      @Override
      protected void onCreate(Bundle savedInstanceState) {
        // Make sure this is before calling super.onCreate
        setTheme(R.style.Theme_MyApp);
        super.onCreate(savedInstanceState);
        // ...
      }
    }