A renderização da interface é o ato de gerar um frame do seu app e mostrá-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 (QPS). Para entender por que 60 QPS é o melhor valor, consulte Padrões de desempenho do Android: por que 60 QPS?. Se você está tentando atingir 90 QPS, essa janela cai para 11 ms, e para 8 ms a 120 QPS.
Se você ultrapassar essa janela em 1 ms, o frame não vai aparecer com
1 ms de atraso, mas será descartado pelo
Choreographer
. Se o app tiver uma renderização lenta da interface, o
sistema será forçado a pular frames, e o usuário vai perceber a renderização lenta do app.
Isso é chamado de instabilidade. Esta página mostra como diagnosticar e corrigir a instabilidade.
Se você está desenvolvendo jogos que não usam o sistema
View
, ignore o
Choreographer
. Nesse caso, a biblioteca
Frame Pacing ajuda os jogos
OpenGL e
Vulkan a renderizar e
corrigir o ritmo dos frames no Android.
Para melhorar a qualidade do app, o Android faz um monitoramento automático em busca de instabilidades e mostra as informações no painel do Android vitals. Para saber como os dados são coletados, consulte Monitorar a qualidade técnica do app com o Android vitals.
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 executar 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ê executá-lo em todos os casos de uso no app, poderá receber um número tão grande de dados que será difícil fazer uma análise. Tanto a inspeção visual quanto o Systrace detectam instabilidade no dispositivo local. Se a instabilidade não puder ser reproduzida em dispositivos locais, crie um monitoramento de desempenho personalizado para avaliar partes específicas do app em dispositivos executados em campo.
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 interface.
Confira algumas dicas para realizar inspeções visuais:
- Execute uma versão de lançamento do app ou pelo menos uma versão não depurável. A execução do ART desativa várias otimizações importantes para que haja suporte a recursos de depuração. Por isso, confira se você está analisando algo semelhante ao que vai aparecer para o usuário.
- Ative a Criação do perfil de renderização de GPU. A Criação do perfil de renderização de GPU mostra barras na tela que fornecem uma representação visual do tempo necessário para renderizar os frames de uma janela da interface 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ê saiba 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.
- Percorra os componentes que são fontes comuns de instabilidade, como
RecyclerView
. - Faça uma inicialização a frio do app.
- Execute o app em um dispositivo mais lento para agravar o problema.
Quando você encontrar casos de uso que produzem instabilidade, poderá ter uma boa ideia do que está causando esse problema no app. Se precisar de mais informações, use o Systrace para ter mais detalhes.
Systrace
Embora o Systrace seja uma ferramenta que mostre o que todo o dispositivo está fazendo, ele pode ser útil para identificar instabilidade no app. O Systrace tem uma sobrecarga mínima do sistema, resultando em uma instabilidade realista durante a instrumentação.
Registre um trace com o Systrace enquanto executa o caso de uso de instabilidade no dispositivo. Para saber mais sobre como usar o Systrace, consulte Capturar rastros do sistema na linha de comando. O Systrace é dividido em processos e linhas de execução. Procure o processo do app no Systrace, que será parecido com a Figura 1.
![Exemplo do Systrace](https://developer.android.google.cn/static/topic/performance/vitals/images/systrace.png?authuser=5&hl=pt)
O exemplo do Systrace da Figura 1 contém as seguintes informações para identificar instabilidade:
- O Systrace mostra quando cada frame é exibido e codifica cada frame por cor para destacar os tempos de renderização lenta. Isso ajuda a encontrar frames instáveis com mais precisão do que a fornecida pela inspeção visual. Para saber mais, consulte Inspecionar frames e alertas da interface.
- O Systrace detecta problemas no app e mostra alertas em frames individuais e no painel de alertas. É melhor seguir as instruções do alerta.
- Partes do framework e das bibliotecas do Android, como
RecyclerView
, contêm marcadores de rastreamento. Portanto, a linha do tempo do Systrace mostra quando e em quanto tempo esses métodos são executados na linha de execução da interface.
Depois de analisar a saída do Systrace, poderá haver métodos no app suspeitos
de estarem causando instabilidade. Por exemplo, se a linha do tempo mostrar que um
frame lento é causado pela demora da RecyclerView
, você poderá adicionar eventos de rastreamento
personalizados ao código relevante e
executar o Systrace novamente para receber mais informações. No novo Systrace, a linha do tempo mostra
quando os métodos do app são chamados e quanto tempo eles levam para ser executados.
Se o Systrace não mostrar detalhes do motivo por que o trabalho da linha de execução de interface está demorando, use o Android CPU Profiler para registrar um rastreamento de método instrumentado ou de exemplo. Geralmente, os rastreamentos de método não são bons para identificar instabilidade porque produzem falsos positivos devido à 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 app que estão demorando mais. Depois de identificar esses métodos, adicione marcadores de trace e execute novamente o Systrace para conferir se eles estão causando instabilidade.
Para saber mais, consulte Entender o Systrace.
Monitoramento de desempenho personalizado
Se não for possível reproduzir a instabilidade em um dispositivo local, crie um monitoramento de desempenho personalizado no app para 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 Introdução ao Monitoramento de desempenho para Android.
Frames congelados
Os frames congelados são frames da IU que levam mais de 700 ms para serem renderizados. Isso é um problema porque seu app parece estar travado e não responde à entrada do usuário por quase um segundo enquanto o frame está renderizando. Recomendamos otimizar os apps para renderizar um frame em até 16 ms para garantir uma interface suave. No entanto, durante a inicialização do app ou ao fazer a transição para outra tela, é normal que o frame inicial demore mais de 16 ms para ser renderizado, porque seu app precisa inflar as visualizações, organizar a tela e realizar o desenho inicial do zero. É por isso que o Android rastreia frames congelados separadamente da renderização lenta. Nenhum frame no seu app deve levar mais de 700 ms para renderizar.
Para ajudar a melhorar a qualidade, o Android monitora automaticamente o app em busca de frames congelados e mostra as informações no painel do Android vitals. Para saber como os dados são coletados, consulte Monitorar a qualidade técnica do app com o Android vitals.
Os frames congelados são uma forma extrema de renderização lenta, por isso o procedimento para diagnosticar e corrigir o problema é o mesmo.
Como rastrear instabilidades
O FrameTimeline no Perfetto (link em inglês) pode ajudar a rastrear frames lentos ou congelados.
Relação entre frames lentos ou congelados e ANRs
Frames lentos ou congelados e ANRs são diferentes formas de instabilidade que podem ser encontradas no app. Consulte a tabela abaixo para entender a diferença.
Frames lentos | Frames congelados | ANRs | |
---|---|---|---|
Tempo de renderização | Entre 16 ms e 700 ms | Entre 700 ms e 5 s | Maior que 5 s |
Área de impacto visível para os usuários |
|
|
|
Rastrear frames lentos e frames congelados separadamente
Durante a inicialização do app ou ao fazer a transição para uma tela diferente, é normal que o frame inicial demore mais de 16 ms para ser renderizado, porque o app precisa inflar as visualizações, organizar a tela e fazer o desenho inicial do zero.
Práticas recomendadas para priorizar e resolver instabilidades
Siga estas práticas recomendadas ao tentar resolver uma instabilidade no seu app:
- Identifique e resolva os pontos de instabilidade mais fáceis de reproduzir.
- Priorize ANRs. Embora os frames lentos ou congelados possam fazer com que o app pareça lento, os ANRs fazem com que ele pare de responder.
- A renderização lenta é difícil de reproduzir, mas você pode começar eliminando frames congelados por 700ms. Isso é mais comum enquanto o app está inicializando ou mudando de tela.
Corrigir a instabilidade
Para corrigir a instabilidade, inspecione quais frames não estão sendo concluídos em 16 ms e verifique o que
está errado. Confira se Record View#draw
ou Layout
estão demorando muito em
alguns frames. Consulte Fontes comuns de instabilidade para saber mais sobre esses e
outros problemas.
Para evitar instabilidade, execute tarefas de longa duração de forma assíncrona fora da linha de execução de interface. Esteja sempre ciente de qual linha de execução seu código está gerando e tenha cuidado ao postar tarefas não triviais na linha de execução principal.
Se você tem uma interface principal complexa e importante para o app, como a lista de rolagem central, faça testes de instrumentação que possam detectar automaticamente tempos de renderização lenta e execute esses testes com frequência para evitar regressões.
Fontes comuns de instabilidade
As seções a seguir explicam as fontes comuns de instabilidade em apps usando o sistema View
,
bem como práticas recomendadas para resolvê-las. Para saber mais sobre como corrigir
problemas de desempenho com o Jetpack Compose, consulte Desempenho
do Jetpack Compose.
Listas roláveis
ListView
e RecyclerView
,
principalmente, são usadas com frequência para listas de rolagem complexas e mais
suscetíveis à instabilidade. Ambas contêm marcadores do Systrace, então você pode usar a ferramenta
para descobrir se elas estão contribuindo para a instabilidade no app. Transmita o argumento
de linha de comando -a
<your-package-name>
para que as seções de rastreamento da RecyclerView
sejam mostradas, bem 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, clique em
seções rastreadas pela RecyclerView
para conferir uma explicação do trabalho que RecyclerView
está fazendo.
RecyclerView: notifyDataSetChanged()
Caso você perceba todos os itens da RecyclerView
sendo recuperados em um frame
(e, portanto, sendo organizados e mostrados novamente), confira se você não chamou
notifyDataSetChanged()
,
setAdapter(Adapter)
ou swapAdapter(Adapter,
boolean)
para pequenas atualizações. Esses métodos sinalizam que há mudanças em todo
o conteúdo da lista e aparecem no Systrace como RV FullInvalidate. Em vez disso, use
SortedList
ou
DiffUtil
para gerar
atualizações mínimas quando o conteúdo for mudado ou adicionado.
Por exemplo, considere um app que recebe uma nova versão de uma lista de notícias
de um servidor. Quando você posta essas informações no Adapter, é
possível chamar notifyDataSetChanged()
, conforme mostrado no exemplo a seguir:
Kotlin
fun onNewDataArrived(news: List<News>) { myAdapter.news = news myAdapter.notifyDataSetChanged() }
Java
void onNewDataArrived(List<News> news) { myAdapter.setNews(news); myAdapter.notifyDataSetChanged(); }
A desvantagem disso é que, se houver uma mudança trivial, como um único item
adicionado à parte de cima, a RecyclerView
não terá conhecimento dela. Portanto, é recomendado descartar
todo o estado do item armazenado em cache, gerando a necessidade de vincular tudo novamente.
Recomendamos o uso de DiffUtil
, que calcula e envia 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); }
Para informar ao DiffUtil
como inspecionar as listas, defina MyCallback
como
uma implementação de
Callback
.
RecyclerView: RecyclerViews aninhados
É comum aninhar várias instâncias de RecyclerView
, principalmente com uma
lista vertical de listas de rolagem horizontal. Um exemplo disso são as 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,
confira se você está compartilhando
RecyclerView.RecycledViewPool
entre instâncias internas (horizontais) de RecyclerView
. Por padrão, cada
RecyclerView
tem o próprio pool de itens. No entanto, no caso de uma dúzia de
itemViews
na tela ao mesmo tempo, é problemático quando itemViews
não
podem ser compartilhadas pelas diferentes listas horizontais se todas as linhas estão mostrando 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 é possível chamar
setInitialPrefetchItemCount(int)
no
LinearLayoutManager
do componente interno RecyclerView
. Se, por exemplo, você sempre tiver 3,5 itens visíveis
em uma linha, chame innerLLM.setInitialItemPrefetchCount(4)
. Isso vai sinalizar à
RecyclerView
que, quando uma linha horizontal estiver prestes a aparecer na tela, ela precisará
tentar pré-buscar os itens dentro dela se houver tempo livre na linha de execução de interface.
RecyclerView: inflação exagerada ou demora na criação
Na maioria dos casos, o recurso de pré-busca da RecyclerView
pode ajudar a contornar o
custo da inflação fazendo o trabalho com antecedência enquanto a linha de execução de interface está ociosa.
Caso você observe inflação durante um frame, e não em uma seção chamada RV
Prefetch, confira se está testando em um dispositivo com suporte e usando uma versão
recente da Biblioteca de Suporte.
A pré-busca só tem suporte a partir do Android 5.0 (nível 21 da API).
Se você observa com frequência que a inflação está causando instabilidade quando novos itens aparecem na tela, confira
se não há mais tipos de visualização do que o necessário. Quanto menos tipos de visualização houver no
conteúdo da RecyclerView
, menos inflação precisará ser feita quando novos
tipos de item aparecerem na tela. Se possível, mescle tipos de visualização sempre que for razoável. Se
apenas um ícone, cor ou texto for diferente entre os tipos, você poderá fazer essa
mudança no momento da 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. Considere criar
itemViews
com ConstraintLayout
,
o que pode reduzir as visualizações estruturais.
Se você quer otimizar ainda mais o desempenho, 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ê vai perder a simplicidade e os recursos de XML
RecyclerView: vinculação muito demorada
A vinculação, ou seja, onBindViewHolder(VH,
int)
, precisa ser simples e levar menos de um milissegundo para
tudo, exceto os itens mais complexos. Ela precisa extrair itens de objetos simples antigos do Java
(POJO, na sigla em inglês) dos dados de itens internos do adaptador e chamar setters em visualizações no
ViewHolder
. Se RV OnBindView está demorando muito, verifique se você está
fazendo um trabalho mínimo no código de vinculação.
Caso você esteja usando objetos POJO simples para reter dados no adaptador, evite
gravar o código de vinculação em onBindViewHolder
usando a biblioteca Data Binding.
RecyclerView ou ListView: layout/exibição demorando muito
Para problemas com exibição e layout, consulte as seções Desempenho do layout e Desempenho da renderização.
ListView: inflação
Você poderá desativar acidentalmente a reciclagem na ListView
se não tomar cuidado. Caso
você observe inflação toda vez que um item aparece na tela, confira se a
implementação de
Adapter.getView()
está usando, vinculando novamente e retornando o parâmetro convertView
. Se a
implementação de getView()
sempre inflar, o app não terá os benefícios da
reciclagem na ListView
. A estrutura de getView()
precisa ser quase sempre
semelhante à esta implementação:
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
weighted-LinearLayouts
. Cada um
desses layouts pode acionar várias transmissões de medida e layout dos filhos, portanto,
aninhá-los pode levar ao comportamento O(n^2)
na profundidade do aninhamento.
Evite RelativeLayout
ou o recurso de ponderação de LinearLayout
em todos os
nós folha da hierarquia, exceto nos menores. Confira como fazer isso:
- Reorganize suas visualizações estruturais.
- Defina a 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 sem as desvantagens para o desempenho.
Desempenho do layout: frequência
O layout precisa acontecer quando conteúdo novo aparece na tela, por exemplo, quando
um novo item aparece na RecyclerView
. Se um layout significativo está
acontecendo em cada frame, é possível que você esteja animando o layout e provavelmente
causando frames perdidos.
Geralmente, as animações precisam ser executadas em propriedades de exibição de View
, como
estas:
Você pode mudar todas essas opções de maneira muito mais econômica que as propriedades de layout, como usando
padding ou margens. Também é muito mais econômico mudar as propriedades de
exibição de uma visualização chamando um setter, que aciona um
invalidate()
seguido por
draw(Canvas)
no frame seguinte. Isso vai regravar as operações de exibição para a visualização que é
invalidada, sendo também muito mais econômico que o layout.
Desempenho de renderização
A interface do Android funciona em duas etapas:
- Record View#draw na linha de execução de interface, que executa
draw(Canvas)
em todas as visualizações inválidas e pode invocar chamadas em visualizações personalizadas ou no código. - DrawFrame na
RenderThread
, que é executado naRenderThread
nativa, mas funciona com base no trabalho gerado pela etapa Record View#draw.
Desempenho da renderização: linha de execução de interface
Se a etapa Record View#draw estiver demorando muito, normalmente é porque um bitmap está sendo pintado na linha de execução de interface. A pintura de um bitmap usa a renderização da CPU. Portanto, evite 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 de um bitmap geralmente é feita quando um app quer decorar um bitmap antes de mostrá-lo. Às vezes, a decoração pode ser a adição de cantos arredondados:
Kotlin
val paint = Paint().apply { isAntiAlias = true } Canvas(roundedOutputBitmap).apply { // Draw a round rect to define the 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 the 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 interface, poderá
fazê-lo na linha de execução de decodificação em segundo plano. Em alguns casos, como no exemplo
anterior, você pode até mesmo fazer o trabalho no momento da exibição. Portanto, se o código
Drawable
ou
View
tiver esta aparência:
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); }
Você também pode fazer isso para proteção em segundo plano, como ao mostrar um gradiente
sobre o bitmap e filtrar imagens com
ColorMatrixColorFilter
, duas outras
operações comuns para modificar bitmaps.
Caso esteja mostrando algo sobre um bitmap por outro motivo, possivelmente usando-o como
cache, tente mostrar no Canvas
com aceleração de hardware transmitido diretamente para a View
ou o
Drawable
. Se necessário, considere também chamar
setLayerType()
com
LAYER_TYPE_HARDWARE
para armazenar em cache a saída de renderização complexa e ainda aproveitar a renderização da GPU.
Desempenho de renderização: RenderThread
Algumas operações de Canvas
têm gravação econômicas, mas acionam cálculos pesados
na RenderThread
. O Systrace geralmente as destaca com alertas.
Animar caminhos grandes
Quando
Canvas.drawPath()
é chamado no Canvas
com aceleração de hardware transmitido
para View
, o Android mostra esses caminhos primeiro na CPU e os envia para a GPU.
Se você tem caminhos grandes, evite editá-los frame a frame para que possam ser
armazenados em cache e mostrados de forma eficiente.
drawPoints()
,
drawLines()
e drawRect/Circle/Oval/RoundRect()
são mais eficientes.
É melhor usá-los, mesmo que você faça mais chamadas de desenho.
Canvas.clipPath
clipPath(Path)
aciona um comportamento de recorte pesado e geralmente precisa ser evitado. Quando
possível, mostre formas em vez de recortar e gerar não retângulos. Isso
funciona melhor e oferece suporte a antialias. Por exemplo, a chamada
clipPath
a seguir pode ser expressa de maneira diferente:
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();
Em vez disso, expresse o exemplo anterior da seguinte maneira:
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 mostra bitmaps como texturas OpenGL, e, na primeira vez que um bitmap aparece em um frame, ele é enviado para a GPU. Você pode conferir isso no Systrace como Texture upload(id) width x height. Isso pode levar vários milissegundos, como mostrado na Figura 2, mas é necessário para mostrar a imagem com a GPU.
Se levar muito tempo, primeiro confira os números de largura e altura no rastreamento. Confira se o bitmap mostrado não é significativamente maior do que a área em que ele é mostrado na tela. Se for, vai 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 a RenderThread
está ociosa. Você pode fazer isso depois da decodificação ou ao vincular
um bitmap a uma visualização, desde que conheça o bitmap. O ideal seria sua biblioteca de carregamento
de bitmaps fazer isso, mas se você estiver gerenciando sua própria biblioteca ou quiser garantir que
não vai atingir uploads em dispositivos mais recentes, chame prepareToDraw()
no seu
código.
![Um app gasta um tempo significativo em um frame
fazendo upload de um bitmap grande](https://developer.android.google.cn/static/topic/performance/vitals/images/texture-upload.png?authuser=5&hl=pt)
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 precisam ser executadas, quando isso vai acontecer e por quanto tempo.
Às vezes, ocorre instabilidade porque a linha de execução de interface do app está bloqueada ou não está em execução. O Systrace usa cores diferentes, como mostrado na Figura 3, para indicar quando uma linha de execução está em suspensão (cinza), é executável (azul: pode ser executada, mas ainda não foi escolhida pelo programador), está em execução (verde) ou está em suspensão ininterrupta (vermelho ou laranja). Isso é extremamente útil para depurar problemas de instabilidade causados por atrasos de programação da linha de execução.
![Destaca um período em que a linha de execução de interface
está em suspensão](https://developer.android.google.cn/static/topic/performance/vitals/images/thread-sleep.png?authuser=5&hl=pt)
Muitas vezes, as chamadas de binder, o mecanismo de PIC no Android, causam pausas longas na execução do app. Em versões mais recentes do Android, esse é um dos motivos mais comuns para a linha de execução de interface interromper a execução. Geralmente, a correção busca evitar chamadas de funções que fazem chamadas de binder. Se for inevitável, coloque o valor em cache ou mova o trabalho para as linhas de execução em segundo plano. À medida que as bases de código ficam maiores, você pode adicionar acidentalmente uma chamada de binder invocando algum método de nível inferior se não tomar cuidado. No entanto, elas podem ser encontradas e corrigidas pelo rastreamento.
Se você tiver transações de binder, poderá capturar as pilhas de chamadas com os
seguintes comandos do 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 ocorrem
com frequência. O rastreamento periódico pode ajudar a encontrar e corrigir esses problemas à medida que
eles surgem.
![Mostra a linha de execução de interface em suspensão devido a transações de
binder em um movimento de RV. Mantenha sua lógica de vinculação focada e use trace-ipc para
rastrear e remover chamadas de binder.](https://developer.android.google.cn/static/topic/performance/vitals/images/rv-fling.png?authuser=5&hl=pt)
trace-ipc
para rastrear e remover chamadas de binder.Se a atividade de binder não é mostrada, mas você ainda não encontra a linha de execução de interface, confira se não está aguardando algum bloqueio ou uma operação de outra linha de execução. Normalmente, a linha de execução da interface não precisa esperar 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) se tornaram 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á problema em alocar como resposta a um evento raro que não acontece muitas vezes por segundo, como um usuário tocando 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 vai 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 sempre que possível, principalmente em repetições apertadas, a probabilidade de ter problemas será menor.
![Mostra uma GC de 94 ms no HeapTaskDaemon](https://developer.android.google.cn/static/topic/performance/vitals/images/gc.png?authuser=5&hl=pt)
Em versões recentes do Android, a coleta de lixo geralmente é executada em uma linha de execução em segundo plano chamada HeapTaskDaemon. Quantidades significativas de alocação podem significar mais recursos de CPU gastos em GC, como mostrado na Figura 5.
Recomendados para você
- Observação: o texto do link aparece quando o JavaScript está desativado
- Comparar seu app
- Visão geral da avaliação do desempenho de apps
- Práticas recomendadas para a otimização de apps