Criei um exemplo de RecyclerView com base no guia Criação de listas e cartões . Meu adaptador tem uma implementação de padrão apenas para aumentar o layout.
O problema é o baixo desempenho de rolagem. Isso em um RecycleView com apenas 8 itens.
Em alguns testes verifiquei que no Android L esse problema não ocorre. Mas na versão KitKat a diminuição do desempenho é evidente.
java
android
performance
android-recyclerview
falvojr
fonte
fonte
Respostas:
Recentemente enfrentei o mesmo problema, então isso é o que fiz com a biblioteca de suporte RecyclerView mais recente:
Substitua um layout complexo (visualizações aninhadas, RelativeLayout) pelo novo ConstraintLayout otimizado. Ative-o no Android Studio: Vá para SDK Manager -> guia SDK Tools -> Support Repository -> marque ConstraintLayout para Android e Solver para ConstraintLayout. Adicione às dependências:
compile 'com.android.support.constraint:constraint-layout:1.0.2'
Se possível, faça todos os elementos do RecyclerView com a mesma altura . E adicione:
recyclerView.setHasFixedSize(true);
Use os métodos de cache de desenho padrão do RecyclerView e ajuste-os de acordo com o seu caso. Você não precisa de uma biblioteca de terceiros para fazer isso:
recyclerView.setItemViewCacheSize(20); recyclerView.setDrawingCacheEnabled(true); recyclerView.setDrawingCacheQuality(View.DRAWING_CACHE_QUALITY_HIGH);
Se você usar muitas imagens , verifique se o tamanho e a compactação são ideais . O dimensionamento de imagens também pode afetar o desempenho. O problema tem dois lados - a imagem de origem usada e o bitmap decodificado. O exemplo a seguir dá uma dica de como decodificar uma imagem baixada da web:
InputStream is = (InputStream) url.getContent(); BitmapFactory.Options options = new BitmapFactory.Options(); options.inPreferredConfig = Bitmap.Config.RGB_565; Bitmap image = BitmapFactory.decodeStream(is, null, options);
A parte mais importante é especificar
inPreferredConfig
- define quantos bytes serão usados para cada pixel da imagem. Lembre-se de que essa é a opção preferencial . Se a imagem de origem tiver mais cores, ela ainda será decodificada com uma configuração diferente.Certifique-se de que onBindViewHolder () seja o mais barato possível. Você pode definir OnClickListener uma vez dentro
onCreateViewHolder()
e chamar por meio de uma interface um ouvinte fora do conector , passando o item clicado. Dessa forma, você não cria objetos extras o tempo todo. Verifique também sinalizadores e estados, antes de fazer qualquer alteração na visualização aqui.viewHolder.itemView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Item item = getItem(getAdapterPosition()); outsideClickListener.onItemClicked(item); } });
Quando os dados são alterados, tente atualizar apenas os itens afetados . Por exemplo, em vez de invalidar todo o conjunto de dados com
notifyDataSetChanged()
, ao adicionar / carregar mais itens, basta usar:Do site do desenvolvedor Android :
Mas se você precisar usá-lo, mantenha seus itens com ids exclusivos :
adapter.setHasStableIds(true);
Mesmo se você fizer tudo certo, é provável que o RecyclerView ainda não esteja funcionando tão bem quanto você gostaria.
fonte
recyclerView.setItemViewCacheSize(20);
pode melhorar o desempenho. MasrecyclerView.setDrawingCacheEnabled(true);
erecyclerView.setDrawingCacheQuality(View.DRAWING_CACHE_QUALITY_HIGH);
embora! Não tenho certeza se isso vai mudar alguma coisa. Essas sãoView
chamadas específicas que permitem recuperar programaticamente o cache de desenho como um bitmap e usá-lo para sua vantagem posteriormente.RecyclerView
não parece fazer nada a respeito.Resolvi esse problema adicionando o seguinte sinalizador:
https://developer.android.com/reference/android/support/v7/widget/RecyclerView.Adapter.html#setHasStableIds(boolean)
fonte
Descobri pelo menos um padrão que pode prejudicar seu desempenho. Lembre-se de que
onBindViewHolder()
é chamado com freqüência . Portanto, qualquer coisa que você fizer nesse código tem o potencial de interromper seu desempenho. Se o RecyclerView faz alguma personalização, é muito fácil colocar acidentalmente algum código lento neste método.Eu estava mudando as imagens de fundo de cada RecyclerView dependendo da posição. Mas carregar imagens dá um pouco de trabalho, fazendo com que meu RecyclerView fique lento e irregular.
Criar um cache para as imagens funcionou maravilhosamente;
onBindViewHolder()
agora apenas modifica uma referência a uma imagem em cache em vez de carregá-la do zero. Agora o RecyclerView avança.Eu sei que nem todo mundo terá esse problema exato, então não estou me preocupando em carregar o código. Mas, por favor, considere qualquer trabalho que seja feito em seu
onBindViewHolder()
como um gargalo potencial para o desempenho ruim do RecyclerView.fonte
Além da resposta detalhada de @Galya, quero afirmar que, embora possa ser um problema de otimização, também é verdade que ter o depurador ativado pode tornar as coisas muito lentas.
Se você fizer tudo para otimizar seu
RecyclerView
e ainda não funcionar sem problemas, tente alternar sua variante de compilação pararelease
e verifique como funciona em um ambiente de não desenvolvimento (com o depurador desativado)Aconteceu comigo que meu aplicativo estava funcionando lentamente na
debug
variante de compilação, mas assim que mudei para arelease
variante, ele funcionou bem. Isso não significa que você deve desenvolver com arelease
variante de compilação, mas é bom saber que, sempre que estiver pronto para enviar seu aplicativo, ele funcionará perfeitamente.fonte
Eu conversei sobre
RecyclerView
o desempenho de. Aqui estão os slides em inglês e o vídeo gravado em russo .Ele contém um conjunto de técnicas (algumas delas já foram abordadas pela resposta de @Darya ).
Aqui está um breve resumo:
Se os
Adapter
itens tiverem tamanho fixo, defina:recyclerView.setHasFixedSize(true);
Se as entidades de dados puderem ser representadas por long (
hashCode()
por exemplo), defina:adapter.hasStableIds(true);
e implemente:
// YourAdapter.java
@Override
public long getItemId(int position) {
return items.get(position).hashcode(); //id()
}
Nesse caso
Item.id()
, não funcionaria, porque permaneceria o mesmo mesmo seItem
o conteúdo de fosse alterado.PS Isso não é necessário se você estiver usando o DiffUtil!
Use bitmap dimensionado corretamente. Não reinvente a roda e use bibliotecas.
Mais informações sobre como escolher aqui .
Sempre use a versão mais recente do
RecyclerView
. Por exemplo, houve grandes melhorias de desempenho em25.1.0
- prefetch.Mais informações aqui .
Use DiffUtill.
O DiffUtil é obrigatório .
Documentação oficial .
Simplifique o layout do seu item!
Biblioteca minúscula para enriquecer TextViews - TextViewRichDrawable
Veja os slides para uma explicação mais detalhada.
fonte
Não tenho certeza se o uso do
setHasStableId
sinalizador resolverá seu problema. Com base nas informações fornecidas, seu problema de desempenho pode estar relacionado a um problema de memória. O desempenho do seu aplicativo em termos de interface de usuário e memória está bastante relacionado.Na semana passada, descobri que meu aplicativo estava perdendo memória. Descobri isso porque, após 20 minutos de uso do meu aplicativo, percebi que o desempenho da IU estava muito lento. Fechar / abrir uma atividade ou rolar um RecyclerView com um monte de elementos era muito lento. Depois de monitorar alguns dos meus usuários em produção usando http://flowup.io/ , descobri o seguinte:
O tempo de fotogramas foi muito alto e os fotogramas por segundo muito baixos. Você pode ver que alguns quadros precisaram de cerca de 2 segundos para renderizar: S.
Tentando descobrir o que estava causando esse tempo / fps de quadro ruim, descobri que tinha um problema de memória, como você pode ver aqui:
Mesmo quando o consumo médio de memória estava próximo dos 15 MB, ao mesmo tempo, o aplicativo estava perdendo frames.
Foi assim que descobri o problema da IU. Eu tive um vazamento de memória em meu aplicativo causando muitos eventos de coletor de lixo e isso estava causando o mau desempenho da IU porque a VM Android teve que parar meu aplicativo para coletar memória a cada quadro.
Olhando para o código, tive um vazamento dentro de uma visualização personalizada porque não estava cancelando o registro de um ouvinte da instância do Android Choreographer. Depois de lançar a correção, tudo voltou ao normal :)
Se seu aplicativo está perdendo frames devido a um problema de memória, você deve revisar dois erros comuns:
Verifique se o seu aplicativo está alocando objetos dentro de um método invocado várias vezes por segundo. Mesmo que essa alocação possa ser realizada em um local diferente, onde seu aplicativo esteja se tornando lento. Um exemplo poderia ser a criação de novas instâncias de um objeto dentro de um método de visualização personalizada onDraw em onBindViewHolder em seu suporte de visualização do reciclador. Verifique se o seu aplicativo está registrando uma instância no Android SDK, mas não está liberando-a. Registrar um ouvinte em um evento de barramento também pode ser um vazamento possível.
Isenção de responsabilidade: a ferramenta que uso para monitorar meu aplicativo está em desenvolvimento. Eu tenho acesso a essa ferramenta porque sou um dos desenvolvedores :) Se você quiser ter acesso a essa ferramenta, lançaremos uma versão beta em breve! Você pode participar em nosso site: http://flowup.io/ .
Se você quiser usar ferramentas diferentes, pode usar: traveview, dmtracedump, systrace ou o monitor de desempenho Andorid integrado ao Android Studio. Mas lembre-se de que essas ferramentas irão monitorar seu dispositivo conectado e não o resto de seus dispositivos de usuário ou instalações do sistema operacional Android.
fonte
Também é importante verificar o layout pai no qual você colocou em sua Recyclerview. Tive problemas de rolagem semelhantes ao testar o recyclerView em um nestedscrollview. Uma visão que rola em outra visão que pode sofrer no desempenho durante a rolagem
fonte
No meu caso, descobri que a causa notável do atraso é o
#onBindViewHolder()
método de carregamento freqüente de drawable . Resolvi apenas carregando as imagens como Bitmap uma vez dentro do ViewHolder e acessando-as a partir do método mencionado. Isso é tudo que eu fiz.fonte
No meu RecyclerView, eu uso imagens de bitmap para o plano de fundo do meu item_layout.
Tudo o que @Galya disse é verdade (e agradeço a ele por sua ótima resposta). Mas eles não funcionaram para mim.
Isso é o que resolveu meu problema:
BitmapFactory.Options options = new BitmapFactory.Options(); options.inSampleSize = 2; Bitmap bitmap = BitmapFactory.decodeStream(stream, null, options);
Para obter mais informações, leia esta resposta .
fonte
No meu caso, tenho crianças complexas com visão de reciclagem. Portanto, isso afetou o tempo de carregamento da atividade (~ 5 segundos para a renderização da atividade)
Eu carrego o adaptador com postDelayed () -> isso dará um bom resultado para a renderização da atividade. após a atividade de renderização de meu carregamento de reciclagem com suavização
Tente esta resposta,
recyclerView.postDelayed(new Runnable() { @Override public void run() { recyclerView.setAdapter(mAdapter); } },100);
fonte
Vejo nos comentários que você já está implementando o
ViewHolder
padrão, mas postarei um exemplo de adaptador aqui que usa oRecyclerView.ViewHolder
padrão para que você possa verificar se está integrando de forma semelhante, novamente seu construtor pode variar dependendo de suas necessidades, aqui é um exemplo:public class RecyclerAdapter extends RecyclerView.Adapter<RecyclerAdapter.ViewHolder> { Context mContext; List<String> mNames; public RecyclerAdapter(Context context, List<String> names) { mContext = context; mNames = names; } @Override public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) { View view = LayoutInflater.from(viewGroup.getContext()) .inflate(android.R.layout.simple_list_item_1, viewGroup, false); return new ViewHolder(view); } @Override public void onBindViewHolder(ViewHolder viewHolder, int position) { //Populate. if (mNames != null) { String name = mNames.get(position); viewHolder.name.setText(name); } } @Override public int getItemCount() { if (mNames != null) return mNames.size(); else return 0; } /** * Static Class that holds the RecyclerView views. */ static class ViewHolder extends RecyclerView.ViewHolder { TextView name; public ViewHolder(View itemView) { super(itemView); name = (TextView) itemView.findViewById(android.R.id.text1); } } }
Se você tiver qualquer problema de trabalho,
RecyclerView.ViewHolder
certifique-se de ter as dependências apropriadas, que você pode verificar sempre no Gradle.Espero que isso resolva seu problema.
fonte
Isso me ajudou a obter uma rolagem mais suave:
substituir o onFailedToRecycleView (suporte do ViewHolder) no adaptador
e interromper todas as animações em andamento (se houver). "animateview" .clearAnimation ();
lembre-se de retornar verdadeiro;
fonte
Eu resolvi com esta linha de código
recyclerView.setNestedScrollingEnabled(false);
fonte
Adicionando à resposta de @Galya, no bind viewHolder, eu estava usando o método Html.fromHtml (). aparentemente, isso tem impacto no desempenho.
fonte
i Resolva esse problema usando a única linha da biblioteca de Picasso
.em forma()
Picasso.get().load(currentItem.getArtist_image()) .fit()//this wil auto get the size of image and reduce it .placeholder(R.drawable.ic_doctor) .into(holder.img_uploaderProfile, new Callback() { @Override public void onSuccess() { } @Override public void onError(Exception e) { Toast.makeText(context, "Something Happend Wrong Uploader Image", Toast.LENGTH_LONG).show(); } });
fonte