Renderização lenta

Renderização da IU é o ato de gerar um frame do seu app e exibi-lo na tela. Para garantir que a interação do usuário com o app seja suave, ele precisa renderizar frames em menos de 16 ms para atingir 60 quadros por segundo (confira o vídeo por que 60 fps?). Se o app realizar uma renderização lenta da IU, o sistema será forçado a ignorar frames e o usuário perceberá oscilações no app. Chamamos isso de instabilidade.

Para ajudar você a melhorar a qualidade do app, o Android faz um monitoramento automático em busca de instabilidades e exibe as informações no painel do Android vitals. Para informações sobre como os dados são coletados, consulte os documentos do Play Console.

Caso seu app esteja enfrentando instabilidade, esta página fornece orientações sobre como diagnosticar e corrigir o problema.

Identificar instabilidade

Identificar qual parte do código do app está causando instabilidade pode ser difícil. Esta seção descreve três métodos para identificar a instabilidade:

A inspeção visual permite que você execute rapidamente todos os casos de uso no app em alguns minutos, mas não fornece tantos detalhes quanto o Systrace. O Systrace fornece mais detalhes, mas se você o executasse para todos os casos de uso no app, receberia um número tão grande de dados que seria difícil analisá-los. Tanto a inspeção visual quanto o Systrace detectam instabilidade no dispositivo local. Se a instabilidade não puder ser reproduzida em dispositivos locais, é possível criar um monitoramento de desempenho personalizado para avaliar partes específicas do app em dispositivos executados em campo.

Com inspeção visual

A inspeção visual ajuda a identificar os casos de uso que estão produzindo instabilidade. Para realizar uma inspeção visual, abra o app, percorra manualmente as diferentes partes dele e procure instabilidade na IU. Veja algumas dicas ao realizar inspeções visuais:

  • Execute uma versão de lançamento do app ou pelo menos uma versão não depurável. O tempo de execução de ART desativa várias otimizações importantes para que haja suporte a recursos de depuração. Por isso, verifique se você está analisando algo semelhante ao que o usuário verá.
  • Ative a Criação do perfil de renderização de GPU. A Criação do perfil de renderização de GPU exibe barras na tela que fornecem uma representação visual rápida do tempo necessário para renderizar os frames de uma janela da IU em relação ao comparativo de 16 ms por frame. Cada barra tem componentes coloridos que mapeiam uma etapa do pipeline de renderização para que você possa ver qual parte está demorando mais tempo. Por exemplo, se o frame gasta muito tempo com a entrada, analise o código do app referente à entrada do usuário.
  • Há certos componentes, como o RecyclerView, que são uma fonte comum de instabilidade. Caso seu app use esses componentes, é recomendável percorrer essas partes dele.
  • Às vezes, a instabilidade pode ser reproduzida somente com uma inicialização a frio do app.
  • Tente executar o app em um dispositivo mais lento para que o problema fique mais fácil de identificar.

Depois de encontrar casos de uso que produzem instabilidade, você poderá ter uma boa ideia do que está causando esse problema no app. Mas, se precisar de mais informações, use o Systrace para ter mais detalhes.

Com o Systrace

Embora o Systrace seja uma ferramenta que mostra o que todo o dispositivo está fazendo, ele pode ser útil para identificar instabilidade no seu app. O Systrace tem uma sobrecarga mínima do sistema, por isso haverá uma instabilidade realista durante a instrumentação.

Registre um trace com o Systrace enquanto executa o caso de uso de instabilidade no dispositivo. Veja no Tutorial do Systrace instruções de como usá-lo. O Systrace é dividido em processos e linhas de execução. Procure o processo do seu app no Systrace, que será parecido com a figura 1.

Figura 1: Systrace

O Systrace da figura 1 contém as seguintes informações para identificar instabilidade:

  1. O Systrace mostra quando cada frame é desenhado e codifica cada frame por cor para destacar os tempos de renderização lenta. Isso ajuda a encontrar frames individuais instáveis com mais precisão do que a fornecida pela inspeção visual. Para ver mais informações, consulte inspecionar frames e alertas da IU.
  2. O Systrace detecta problemas no app e exibe alertas em frames individuais e no painel de alertas. Seguir as instruções do alerta é a melhor opção.
  3. Partes do framework e das bibliotecas do Android, como RecyclerView, contêm marcadores de rastreamento. Portanto, a linha do tempo do Systrace mostra quando esses métodos são executados na linha de execução de IU e em quanto tempo.

Depois de analisar a saída do Systrace, poderá haver métodos no app que você suspeita que estão causando instabilidade. Por exemplo, se a linha do tempo mostrar que um frame lento é causado pela demora do RecyclerView, você poderá adicionar marcadores de rastros ao código relevante e executar novamente o Systrace para receber mais informações. No novo Systrace, a linha do tempo exibe quando os métodos do app foram chamados e quanto tempo levaram para ser executados.

Se o Systrace não mostrar detalhes do motivo pelo qual o trabalho da linha de execução de IU está demorando, será necessário usar o Android CPU Profiler para gravar um rastreamento de método amostrado ou instrumentado. Geralmente, os rastreamentos de método não são bons para identificar instabilidade, porque geram falsos positivos devido a sobrecarga pesada e não podem perceber quando linhas de execução estão sendo executadas ou estão bloqueadas. No entanto, os rastreamentos de método podem ajudar a identificar os métodos do seu app que estão demorando mais. Depois de identificar esses métodos, adicione marcadores de rastro e execute novamente o Systrace para ver se esses métodos estão causando instabilidade.

Para mais informações, consulte Noções básicas sobre o Systrace.

Com o monitoramento de desempenho personalizado

Se não for possível reproduzir instabilidade em um dispositivo local, você poderá criar um monitoramento de desempenho personalizado no seu app para ajudar a identificar a origem da instabilidade nos dispositivos em campo.

Para fazer isso, colete os tempos de renderização do frame de partes específicas do app com o FrameMetricsAggregator e grave e analise os dados usando o Monitoramento de desempenho do Firebase.

Para saber mais, consulte Usar o Monitoramento de desempenho do Firebase com o Android vitals.

Corrigir a instabilidade

Para corrigir a instabilidade, inspecione quais frames não estão concluindo em 16,7 ms e verifique o que deu errado. O recurso Record View#draw está demorando demais em alguns frames, ou talvez o Layout? Consulte esses e outros problemas na seção Fontes comuns de instabilidade abaixo.

Para evitar a instabilidade, as tarefas de longa execução precisam ser executadas de forma assíncrona fora da linha de execução de IU. Esteja sempre ciente de qual linha de execução seu código está executando e tenha cuidado ao postar tarefas não triviais na linha de execução principal.

Se você tem uma IU principal complexa e importante para o app (como a lista de rolagem central), grave testes de instrumentação que possam detectar automaticamente tempos de renderização lenta e execute esses testes com frequência para evitar regressões. Para ver mais informações, consulte o Codelab de testes de desempenho automatizado.

Fontes comuns de instabilidade

As seções a seguir explicam as fontes comuns de instabilidade em apps e as práticas recomendadas para resolvê-las.

Listas roláveis

Os componentes ListView e, especialmente, RecyclerView são usados frequentemente para listas de rolagem complexas, mais suscetíveis à instabilidade. Ambos contêm marcadores do Systrace, então você pode usar a ferramenta para descobrir se eles estão contribuindo para a instabilidade no app. Certifique-se de passar o argumento de linha de comando -a <your-package-name> para que as seções de rastreamento do RecyclerView sejam exibidas, assim como todos os marcadores de rastros adicionados. Se disponível, siga a orientação dos alertas gerados na saída do Systrace. Dentro do Systrace, você pode clicar nas seções rastreadas pelo RecyclerView para ver uma explicação do trabalho que ele está fazendo.

RecyclerView: notifyDataSetChanged

Caso você perceba todos os itens do RecyclerView sendo recuperados em um frame (e, portanto, sendo organizados e desenhados novamente), certifique-se de não chamar notifyDataSetChanged(), setAdapter(Adapter) ou swapAdapter(Adapter, boolean) para pequenas atualizações. Esses métodos indicam que todo o conteúdo da lista mudou e aparecerá no Systrace como RV FullInvalidate. Em vez disso, use SortedList ou DiffUtil para gerar atualizações mínimas quando um conteúdo for mudado ou adicionado.

Por exemplo, considere um app que recebe uma nova versão de uma lista de notícias do servidor. Quando você posta essa informação no Adapter, é possível chamar notifyDataSetChanged() como mostrado abaixo:

Kotlin

fun onNewDataArrived(news: List<News>) {
    myAdapter.news = news
    myAdapter.notifyDataSetChanged()
}

Java

void onNewDataArrived(List<News> news) {
    myAdapter.setNews(news);
    myAdapter.notifyDataSetChanged();
}

Mas isso tem uma grande desvantagem: se for uma mudança trivial (talvez um único item adicionado ao topo), o RecyclerView não tomará conhecimento. Isso porque a instrução é descartar todo o estado dos itens em cache e, portanto, recuperar tudo.

É muito melhor usar o DiffUtil, que calculará e enviará atualizações mínimas para você.

Kotlin

fun onNewDataArrived(news: List<News>) {
    val oldNews = myAdapter.items
    val result = DiffUtil.calculateDiff(MyCallback(oldNews, news))
    myAdapter.news = news
    result.dispatchUpdatesTo(myAdapter)
}

Java

void onNewDataArrived(List<News> news) {
    List<News> oldNews = myAdapter.getItems();
    DiffResult result = DiffUtil.calculateDiff(new MyCallback(oldNews, news));
    myAdapter.setNews(news);
    result.dispatchUpdatesTo(myAdapter);
}

Basta definir MyCallback como uma implementação de DiffUtil.Callback para informar a DiffUtil como inspecionar suas listas.

RecyclerView: RecyclerViews aninhados

É comum aninhar RecyclerViews, especialmente com uma lista vertical contendo listas de rolagem horizontal, como grades de apps na página principal da Play Store. Isso pode funcionar muito bem, mas também há muitas visualizações em movimento. Caso haja uma grande quantidade de itens internos inflados ao rolar a página para baixo pela primeira vez, verifique se você está compartilhando os RecyclerView.RecycledViewPools entre RecyclerViews internos (horizontais). Por padrão, cada RecyclerView tem seu próprio pool de itens. No entanto, com uma dúzia de itemViews na tela ao mesmo tempo, é problemático quando os itemViews não podem ser compartilhados pelas listas horizontais, se todas as linhas estiverem exibindo tipos semelhantes de visualizações.

Kotlin

class OuterAdapter : RecyclerView.Adapter<OuterAdapter.ViewHolder>() {

    ...

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        // inflate inner item, find innerRecyclerView by ID…
        val innerLLM = LinearLayoutManager(parent.context, LinearLayoutManager.HORIZONTAL, false)
        innerRv.apply {
            layoutManager = innerLLM
            recycledViewPool = sharedPool
        }
        return OuterAdapter.ViewHolder(innerRv)
    }
    ...

Java

class OuterAdapter extends RecyclerView.Adapter<OuterAdapter.ViewHolder> {
    RecyclerView.RecycledViewPool sharedPool = new RecyclerView.RecycledViewPool();

    ...

    @Override
    public void onCreateViewHolder(ViewGroup parent, int viewType) {
        // inflate inner item, find innerRecyclerView by ID…
        LinearLayoutManager innerLLM = new LinearLayoutManager(parent.getContext(),
                LinearLayoutManager.HORIZONTAL);
        innerRv.setLayoutManager(innerLLM);
        innerRv.setRecycledViewPool(sharedPool);
        return new OuterAdapter.ViewHolder(innerRv);

    }
    ...

Se você quiser otimizar ainda mais, também pode chamar setInitialPrefetchItemCount(int) no LinearLayoutManager interno do RecyclerView. Se, por exemplo, sempre houver 3,5 itens visíveis em uma linha, chame innerLLM.setInitialItemPrefetchCount(4);. Isso sinalizará ao RecyclerView que, quando uma linha horizontal estiver prestes a aparecer na tela, ele precisará tentar pré-buscar os itens dentro dela, se houver tempo livre na linha de execução de IU.

RecyclerView: excesso de inflação/criação demorando muito

O recurso de pré-busca do RecyclerView deve ajudar a solucionar o custo da inflação, na maioria dos casos, fazendo o trabalho com antecedência, enquanto a linha de execução de IU está ociosa. Caso você observe inflação durante um frame (e não em uma seção chamada RV Prefetch), certifique-se de que está fazendo o teste em um dispositivo recente e usando uma versão recente da Biblioteca de Suporte. A pré-busca atualmente só é compatível com o Android 5.0, API de nível 21 e mais recentes.

Se você observa frequentemente que a inflação está causando instabilidade quando novos itens aparecem na tela, verifique se não há mais tipos de visualização do que o necessário. Quanto menos tipos de visualizações houver em um conteúdo do RecyclerView, menos inflação precisará ser feita quando novos tipos de itens aparecerem na tela. Se possível, mescle tipos de visualização quando for razoável. Se apenas um ícone, cor ou texto for diferente entre os tipos, você poderá fazer essa alteração no tempo de vinculação e evitar inflação, reduzindo o consumo de memória do app ao mesmo tempo.

Caso não haja problemas com os tipos de visualização, tente reduzir o custo da sua inflação. Reduzir visualizações de contêiner e estruturais desnecessárias pode ajudar. Crie itemViews com ConstraintLayout, o que pode facilitar a redução de visualizações estruturais. Se você quer realmente otimizar o desempenho, se as suas hierarquias de itens são simples e você não precisa de recursos complexos de temas e estilo, chame os construtores. No entanto, muitas vezes não vale a pena fazer isso, porque você perderá a simplicidade e os recursos de XML.

RecyclerView: vinculação muito demorada

É preciso que a vinculação (ou seja, onBindViewHolder(VH, int)) seja muito simples e leve muito menos de um milissegundo para todos os itens, exceto os mais complexos. Ela precisa simplesmente pegar os itens POJO dos dados de itens internos do adaptador e chamar setters em visualizações do ViewHolder. Se o RV OnBindView está demorando muito, verifique se você está fazendo um trabalho mínimo no seu código de vinculação.

Caso você esteja usando objetos POJO simples para reter dados no adaptador, evite completamente gravar o código de vinculação em onBindViewHolder usando a biblioteca Data Binding.

RecyclerView ou ListView: layout/desenho demorando muito

Para problemas com desenho e layout, consulte as seções sobre Layout e Desempenho da renderização.

ListView: inflação

É fácil desativar acidentalmente a reciclagem no ListView se não tomar cuidado. Caso você observe inflação toda vez que um item aparece na tela, verifique se a implementação de Adapter.getView() está usando, vinculando novamente e retornando o parâmetro convertView. Se a implementação do getView() sempre inflar, o app não terá os benefícios da reciclagem no ListView. A estrutura da getView() precisa ser quase sempre semelhante à implementação abaixo:

Kotlin

fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
    return (convertView ?: layoutInflater.inflate(R.layout.my_layout, parent, false)).apply {
        // … bind content from position to convertView …
    }
}

Java

View getView(int position, View convertView, ViewGroup parent) {

    if (convertView == null) {
        // only inflate if no convertView passed
        convertView = layoutInflater.inflate(R.layout.my_layout, parent, false)
    }
    // … bind content from position to convertView …
    return convertView;
}

Desempenho do layout

Se o Systrace mostra que o segmento Layout do Choreographer#doFrame está trabalhando muito ou com muita frequência, isso significa que você está enfrentando problemas de desempenho do layout. O desempenho do layout do app depende de qual parte da hierarquia de visualização tem parâmetros ou entradas de layout que mudam.

Desempenho do layout: custo

Se os segmentos forem mais longos que alguns milésimos de segundo, é possível que você esteja tendo o pior desempenho de aninhamento para RelativeLayouts ou LinearLayouts ponderados. Cada um desses layouts pode acionar várias passagens de medida/layout dos filhos, portanto, aninhá-los pode levar ao comportamento O(n^2) na profundidade do aninhamento. Tente evitar RelativeLayout ou o recurso de ponderação de LinearLayout em todos os nós folha da hierarquia, exceto nos menores. Há algumas maneiras de fazer isso:

  • Você pode reorganizar as visualizações estruturais.
  • Você pode definir uma lógica de layout personalizada. Acesse Otimizar hierarquias de layout para consultar um exemplo específico. Você pode tentar converter para o ConstraintLayout, que fornece recursos parecidos e sem as desvantagens para o desempenho.

Desempenho do layout: frequência

Espera-se que o layout aconteça quando conteúdo novo aparece na tela, por exemplo, quando um novo item rola para a visualização no RecyclerView. Se um layout significativo está acontecendo em cada frame, é possível que você esteja animando o layout, o que provavelmente causa frames perdidos. Geralmente, as animações precisam ser executadas nas propriedades de desenho de View (por exemplo, setTranslationX/Y/Z(), setRotation(), setAlpha() etc.). Todas elas podem ser alteradas de maneira muito mais econômica do que as propriedades de layout, como padding ou margens. Também é muito mais barato mudar as propriedades de desenho de uma visualização, geralmente chamando um setter, que aciona um invalidate(), seguido por um draw(Canvas) no frame seguinte. Isso regravará as operações de desenho para a visualização que é invalidada e também é geralmente muito mais econômico que o layout.

Desempenho de renderização

A IU do Android funciona em duas fases: Record View#draw, na linha de execução de IU, e DrawFrame, no RenderThread. A primeira é executada no draw(Canvas) em cada View invalidada e pode invocar chamadas para visualizações personalizadas ou para seu código. A segunda é executada no RenderThread nativo, mas funciona com base no trabalho gerado pela fase de Record View#draw.

Desempenho da renderização: linha de execução de IU

Se a fase Record View#draw estiver demorando muito, normalmente é porque um bitmap está sendo pintado na linha de execução de IU. A pintura em um bitmap usa a renderização da CPU, então você precisa evitar isso quando possível. Você pode usar o rastreamento de método com o Android CPU Profiler para conferir se esse é o problema.

A pintura em um bitmap geralmente é feita quando um app quer decorar um bitmap antes de exibi-lo. Às vezes, é feita uma decoração como a adição de cantos arredondados:

Kotlin

val paint = Paint().apply {
    isAntiAlias = true
}
Canvas(roundedOutputBitmap).apply {
    // draw a round rect to define shape:
    drawRoundRect(
            0f,
            0f,
            roundedOutputBitmap.width.toFloat(),
            roundedOutputBitmap.height.toFloat(),
            20f,
            20f,
            paint
    )
    paint.xfermode = PorterDuffXfermode(PorterDuff.Mode.MULTIPLY)
    // multiply content on top, to make it rounded
    drawBitmap(sourceBitmap, 0f, 0f, paint)
    setBitmap(null)
    // now roundedOutputBitmap has sourceBitmap inside, but as a circle
}

Java

Canvas bitmapCanvas = new Canvas(roundedOutputBitmap);
Paint paint = new Paint();
paint.setAntiAlias(true);
// draw a round rect to define shape:
bitmapCanvas.drawRoundRect(0, 0,
        roundedOutputBitmap.getWidth(), roundedOutputBitmap.getHeight(), 20, 20, paint);
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.MULTIPLY));
// multiply content on top, to make it rounded
bitmapCanvas.drawBitmap(sourceBitmap, 0, 0, paint);
bitmapCanvas.setBitmap(null);
// now roundedOutputBitmap has sourceBitmap inside, but as a circle

Se esse for o tipo de trabalho que você faz na linha de execução de IU, poderá fazê-lo na linha de execução de decodificação em segundo plano. Em alguns casos como esse, você poderá até mesmo fazer o trabalho no momento do desenho. Então, se seu código de Drawable ou View for parecido com este:

Kotlin

fun setBitmap(bitmap: Bitmap) {
    mBitmap = bitmap
    invalidate()
}

override fun onDraw(canvas: Canvas) {
    canvas.drawBitmap(mBitmap, null, paint)
}

Java

void setBitmap(Bitmap bitmap) {
    mBitmap = bitmap;
    invalidate();
}

void onDraw(Canvas canvas) {
    canvas.drawBitmap(mBitmap, null, paint);
}

Você poderá substituí-lo por este:

Kotlin

fun setBitmap(bitmap: Bitmap) {
    shaderPaint.shader = BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP)
    invalidate()
}

override fun onDraw(canvas: Canvas) {
    canvas.drawRoundRect(0f, 0f, width, height, 20f, 20f, shaderPaint)
}

Java

void setBitmap(Bitmap bitmap) {
    shaderPaint.setShader(
            new BitmapShader(bitmap, TileMode.CLAMP, TileMode.CLAMP));
    invalidate();
}

void onDraw(Canvas canvas) {
    canvas.drawRoundRect(0, 0, width, height, 20, 20, shaderPaint);
}

Observe que isso também pode ser feito para proteção em segundo plano (desenhando um gradiente na parte superior do bitmap) e filtragem de imagem (com ColorMatrixColorFilter), duas outras operações comuns feitas para modificar bitmaps.

Caso esteja desenhando em um bitmap por outro motivo, possivelmente usando-o como cache, desenhe diretamente para o Canvas com aceleração de hardware passado para a View ou Drawable. Se necessário, chame também setLayerType() com LAYER_TYPE_HARDWARE para armazenar em cache a renderização complexa e aproveitar a renderização da GPU.

Desempenho de renderização: RenderThread

Algumas operações de Canvas são baratas para gravar, mas acionam cálculos dispendiosos no RenderThread. O Systrace geralmente as destaca com alertas.

Canvas.saveLayer()

Evite Canvas.saveLayer(): ele pode acionar renderizações caras, sem cache e fora da tela de cada frame. Embora o desempenho tenha sido aprimorado no Android 6.0, com otimizações para evitar a renderização de chave de destino na GPU, ainda é recomendável evitar essa API cara, se possível, ou no mínimo passar o Canvas.CLIP_TO_LAYER_SAVE_FLAG ou chamar uma variante que não aceite sinalizações.

Animar caminhos grandes

Quando Canvas.drawPath() é chamado no Canvas com aceleração de hardware passado para Views, o Android desenha esses caminhos primeiro na CPU e os envia para a GPU. Se você tem caminhos grandes, evite editá-los frame a frame para que eles possam ser armazenados em cache e desenhados de forma eficiente. drawPoints(), drawLines() e drawRect/Circle/Oval/RoundRect() são mais eficientes. Então, é melhor usá-los, mesmo que, para isso, você use mais chamadas de desenho.

Canvas.clipPath

clipPath(Path) aciona um comportamento de recorte caro e geralmente precisa ser evitado. Quando possível, opte por desenhar formas, em vez de recortar e gerar não retângulos. Isso funciona melhor e é compatível com antialias. Por exemplo, a seguinte chamada de clipPath:

Kotlin

canvas.apply {
    save()
    clipPath(circlePath)
    drawBitmap(bitmap, 0f, 0f, paint)
    restore()
}

Java

canvas.save();
canvas.clipPath(circlePath);
canvas.drawBitmap(bitmap, 0f, 0f, paint);
canvas.restore();

Pode ser expressa como:

Kotlin

paint.shader = BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP)
// at draw time:
canvas.drawPath(circlePath, mPaint)

Java

// one time init:
paint.setShader(new BitmapShader(bitmap, TileMode.CLAMP, TileMode.CLAMP));
// at draw time:
canvas.drawPath(circlePath, mPaint);

Uploads de bitmap

O Android exibe bitmaps como texturas OpenGL, e, na primeira vez que um bitmap é exibido em um frame, ele é enviado para a GPU. Você pode ver isso no Systrace como Texture upload(id) width x height. Isso pode levar vários milissegundos (veja a Figura 2), mas é necessário para exibir a imagem com a GPU.

Se levar muito tempo, primeiro verifique os números de largura e altura no rastreamento. Certifique-se de que o bitmap exibido não seja significativamente maior do que a área em que ele é mostrado na tela. Se for, haverá desperdício de tempo de upload e memória. Geralmente, as bibliotecas de carregamento de bitmap fornecem meios fáceis de solicitar um bitmap de tamanho adequado.

No Android 7.0, o código de carregamento de bitmap (geralmente feito por bibliotecas) pode chamar prepareToDraw() para acionar um upload antecipado, antes que seja necessário. Dessa forma, o upload acontece cedo, enquanto o RenderThread está ocioso. Isso pode ser feito após a decodificação ou ao vincular um bitmap a um View, contanto que você conheça o bitmap. Idealmente, sua biblioteca de carregamento de bitmaps fará isso, mas se você estiver gerenciando sua própria biblioteca ou quiser garantir que não atinja uploads em dispositivos mais recentes, chame prepareToDraw() no seu código.

Figura 2: um app gasta muito tempo em um frame fazendo upload de um bitmap grande. Reduza o tamanho dele ou acione-o com antecedência quando decodificado com prepareToDraw().

Atrasos na programação de linhas de execução

O programador de linhas de execução é a parte do sistema operacional Android responsável por decidir quais linhas de execução do sistema devem ser executadas, quando e por quanto tempo. Às vezes, ocorre instabilidade porque a linha de execução de IU do app está bloqueada ou não está em execução. O Systrace usa cores diferentes (confira a figura 3) para indicar quando uma linha de execução está em Em suspensão (cinza), Executável (azul: pode ser executada, mas o programador ainda não a executou), Em execução (verde) ou Em suspensão ininterrupta (vermelho ou laranja). Isso é extremamente útil para depurar problemas de instabilidade causados por atrasos de programação de linha de execução.

Figura 3: destaca um período em que a linha de execução de IU está em suspensão.

Muitas vezes, pausas longas na execução do app são causadas por chamadas do binder, o mecanismo de comunicação entre processos (IPC) do Android. Em versões recentes do Android, esse é um dos motivos mais comuns para a linha de execução de IU parar de ser executada. Geralmente, a correção visa evitar chamadas de funções que fazem chamadas de binder. Se for inevitável, coloque o valor em cache ou mova o trabalho para linhas de execução em segundo plano. À medida que as bases de código ficam maiores, é fácil adicionar acidentalmente uma chamada de binder invocando algum método de nível inferior se você não tiver cuidado, mas é igualmente fácil encontrá-las e corrigi-las por meio de rastreamento.

Se você tiver transações de binder, poderá capturar as pilhas de chamadas com os seguintes comandos adb:

$ adb shell am trace-ipc start
… use the app - scroll/animate ...
$ adb shell am trace-ipc stop --dump-file /data/local/tmp/ipc-trace.txt
$ adb pull /data/local/tmp/ipc-trace.txt

Às vezes, chamadas aparentemente inofensivas como getRefreshRate() podem acionar transações de binder e causar grandes problemas quando são chamadas com frequência. O rastreamento periódico pode ajudar você a encontrar e corrigir esses problemas rapidamente à medida que surgirem.

Figura 4: mostra a linha de execução de IU inativa devido a transações de binder em um movimento de RV. Mantenha sua lógica de vinculação simples e use trace-ipc para rastrear e remover chamadas de binder.

Se você não observar atividade de binder, mas ainda não observar a linha de execução de IU, certifique-se de que não está aguardando algum bloqueio ou outra operação de outra linha de execução. Normalmente, a linha de execução de IU não precisa esperar por resultados de outras linhas de execução. Elas é que precisam postar informações.

Alocação de objetos e coleta de lixo

A alocação de objetos e a coleta de lixo (GC, na sigla em inglês) tornaram-se um problema menos significativo desde que o ART foi lançado como o ambiente de execução padrão do Android 5.0, mas ainda é possível sobrecarregar as linhas de execução com esse trabalho extra. Não há problemas em alocar em resposta a um evento raro que não acontece muitas vezes por segundo, como um usuário clicando em um botão, mas lembre-se de que cada alocação tem um custo. Se a alocação estiver em uma repetição que é chamada com frequência, considere evitá-la para aliviar a carga de GC.

O Systrace mostrará se a coleta de lixo está sendo executada com frequência e o Android Memory Profiler poderá mostrar a origem das alocações. Se você evitar alocações quando puder, especialmente em repetições apertadas, provavelmente não terá problemas.

Figura 5: mostra uma coleta de lixo de 94 ms na linha de execução HeapTaskDaemon.

Em versões recentes do Android, a coleta de lixo geralmente é executada em uma linha de execução em segundo plano chamada HeapTaskDaemon. Observe que quantidades significativas de alocação podem significar mais recursos de CPU gastos em GC, como mostrado na figura 5.