He creado un ejemplo de RecyclerView basándome en la guía Creación de listas y tarjetas . Mi adaptador tiene una implementación de patrón solo para inflar el diseño.
El problema es el rendimiento deficiente del desplazamiento. Esto en un RecycleView con solo 8 elementos.
En algunas pruebas verifiqué que en Android L no ocurre este problema. Pero en la versión KitKat, la disminución del rendimiento es evidente.
java
android
performance
android-recyclerview
falvojr
fuente
fuente
Respuestas:
Recientemente me enfrenté al mismo problema, así que esto es lo que hice con la última biblioteca de soporte de RecyclerView:
Reemplace un diseño complejo (vistas anidadas, RelativeLayout) con el nuevo ConstraintLayout optimizado. Actívelo en Android Studio: Vaya a SDK Manager -> pestaña SDK Tools -> Support Repository -> marque ConstraintLayout para Android y Solver para ConstraintLayout. Agregue a las dependencias:
compile 'com.android.support.constraint:constraint-layout:1.0.2'
Si es posible, haga que todos los elementos de RecyclerView tengan la misma altura . Y añadir:
recyclerView.setHasFixedSize(true);
Utilice los métodos predeterminados de caché de dibujo de RecyclerView y ajústelos según su caso. No necesita una biblioteca de terceros para hacerlo:
recyclerView.setItemViewCacheSize(20); recyclerView.setDrawingCacheEnabled(true); recyclerView.setDrawingCacheQuality(View.DRAWING_CACHE_QUALITY_HIGH);
Si usa muchas imágenes , asegúrese de que su tamaño y compresión sean óptimos . El escalado de imágenes también puede afectar el rendimiento. Hay dos lados del problema: la imagen de origen utilizada y el mapa de bits decodificado. El siguiente ejemplo le da una pista de cómo decodificar una imagen, descargada de la 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);
La parte más importante es especificar
inPreferredConfig
: define cuántos bytes se utilizarán para cada píxel de la imagen. Tenga en cuenta que esta es una opción preferida . Si la imagen de origen tiene más colores, aún se decodificará con una configuración diferente.Asegúrese de que onBindViewHolder () sea lo más barato posible. Puede configurar OnClickListener una vez
onCreateViewHolder()
y llamar a través de una interfaz a un oyente fuera del Adaptador, pasando el elemento seleccionado. De esta manera, no crea objetos adicionales todo el tiempo. También verifique las banderas y los estados, antes de realizar cambios en la vista aquí.viewHolder.itemView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Item item = getItem(getAdapterPosition()); outsideClickListener.onItemClicked(item); } });
Cuando se modifiquen los datos, intente actualizar solo los elementos afectados . Por ejemplo, en lugar de invalidar todo el conjunto de datos con
notifyDataSetChanged()
, al agregar / cargar más elementos, simplemente use:Desde el sitio web para desarrolladores de Android :
Pero si necesita usarlo, mantenga sus artículos con identificadores únicos :
adapter.setHasStableIds(true);
Incluso si hace todo bien, lo más probable es que RecyclerView todavía no funcione tan bien como le gustaría.
fuente
recyclerView.setItemViewCacheSize(20);
pueden mejorar el rendimiento. ¡PerorecyclerView.setDrawingCacheEnabled(true);
yrecyclerView.setDrawingCacheQuality(View.DRAWING_CACHE_QUALITY_HIGH);
aunque! No estoy seguro de que esto cambie algo. Estas sonView
llamadas específicas que le permiten recuperar mediante programación el caché de dibujo como un mapa de bits y utilizarlo para su ventaja más adelante.RecyclerView
no parece hacer nada al respecto.Resolví este problema agregando la siguiente bandera:
https://developer.android.com/reference/android/support/v7/widget/RecyclerView.Adapter.html#setHasStableIds(boolean)
fuente
Descubrí al menos un patrón que puede matar tu desempeño. Recuerda que
onBindViewHolder()
se llama con frecuencia . Entonces, cualquier cosa que haga en ese código tiene el potencial de detener su desempeño. Si su RecyclerView hace alguna personalización, es muy fácil poner accidentalmente un código lento en este método.Estaba cambiando las imágenes de fondo de cada RecyclerView según la posición. Pero cargar imágenes requiere un poco de trabajo, lo que hace que mi RecyclerView sea lento y desigual.
La creación de un caché para las imágenes funcionó de maravilla;
onBindViewHolder()
ahora solo modifica una referencia a una imagen en caché en lugar de cargarla desde cero. Ahora el RecyclerView avanza rápidamente.Sé que no todos tendrán este problema exacto, por lo que no me molesto en cargar el código. Pero considere cualquier trabajo que se realice en usted
onBindViewHolder()
como un posible cuello de botella para un rendimiento deficiente de RecyclerView.fuente
Además de la respuesta detallada de @ Galya, quiero afirmar que, aunque puede ser un problema de optimización, también es cierto que tener el depurador habilitado puede ralentizar mucho las cosas.
Si hace todo lo posible para optimizar su
RecyclerView
y aún no funciona correctamente, intente cambiar su variante de compilación arelease
y verifique cómo funciona en un entorno que no es de desarrollo (con el depurador deshabilitado).Me pasó que mi aplicación funcionaba lentamente en la
debug
variante de compilación, pero tan pronto como cambié a larelease
variante, funcionó sin problemas. Esto no significa que deba desarrollar con larelease
variante de compilación, pero es bueno saber que siempre que esté listo para enviar su aplicación, funcionará bien.fuente
Tuve una charla sobre
RecyclerView
su desempeño. Aquí hay diapositivas en inglés y video grabado en ruso .Contiene un conjunto de técnicas (algunas de ellas ya están cubiertas por la respuesta de @ Darya ).
Aquí hay un breve resumen:
Si los
Adapter
artículos tienen un tamaño fijo, configure:recyclerView.setHasFixedSize(true);
Si las entidades de datos pueden ser representadas por long (
hashCode()
por ejemplo), entonces configure:adapter.hasStableIds(true);
e implemente:
// YourAdapter.java
@Override
public long getItemId(int position) {
return items.get(position).hashcode(); //id()
}
En este caso
Item.id()
no funcionaría, porque permanecería igual incluso siItem
el contenido ha cambiado.PD ¡Esto no es necesario si está utilizando DiffUtil!
Utilice mapa de bits correctamente escalado. No reinvente la rueda y use bibliotecas.
Más información sobre cómo elegir aquí .
Utilice siempre la última versión de
RecyclerView
. Por ejemplo, hubo enormes mejoras de rendimiento en la25.1.0
captación previa.Más info aquí .
Utilice DiffUtill.
DiffUtil es imprescindible .
Documentación oficial .
¡Simplifique el diseño de su artículo!
Pequeña biblioteca para enriquecer TextViews - TextViewRichDrawable
Consulte las diapositivas para obtener una explicación más detallada.
fuente
No estoy realmente seguro de si el uso de la
setHasStableId
bandera solucionará su problema. Según la información que proporcione, su problema de rendimiento podría estar relacionado con un problema de memoria. El rendimiento de su aplicación en términos de interfaz de usuario y memoria está bastante relacionado.La semana pasada descubrí que mi aplicación estaba perdiendo memoria. Descubrí esto porque después de 20 minutos usando mi aplicación noté que la interfaz de usuario funcionaba muy lento. Cerrar / abrir una actividad o desplazarse por un RecyclerView con un montón de elementos fue realmente lento. Después de monitorear a algunos de mis usuarios en producción usando http://flowup.io/ encontré esto:
El tiempo de fotogramas fue realmente muy alto y los fotogramas por segundo realmente muy bajos. Puede ver que algunos fotogramas necesitan unos 2 segundos para renderizarse: S.
Tratando de averiguar qué estaba causando este mal tiempo de fotograma / fps, descubrí que tenía un problema de memoria como puede ver aquí:
Incluso cuando el consumo medio de memoria estaba cerca de los 15 MB al mismo tiempo, la aplicación estaba perdiendo fotogramas.
Así fue como descubrí el problema de la interfaz de usuario. Tuve una pérdida de memoria en mi aplicación que causó muchos eventos de recolección de basura y eso fue lo que causó el mal rendimiento de la interfaz de usuario porque la máquina virtual de Android tuvo que detener mi aplicación para recopilar memoria en cada fotograma.
Al mirar el código, tuve una fuga dentro de una vista personalizada porque no estaba anulando el registro de un oyente de la instancia de Android Choreographer. Después de lanzar la solución, todo se volvió normal :)
Si su aplicación está perdiendo fotogramas debido a un problema de memoria, debe revisar dos errores comunes:
Revise si su aplicación asigna objetos dentro de un método invocado varias veces por segundo. Incluso si esta asignación se puede realizar en un lugar diferente donde su aplicación se está volviendo lenta. Un ejemplo podría ser la creación de nuevas instancias de un objeto dentro de un método de vista personalizado onDraw en onBindViewHolder en su titular de vista de vista de reciclador. Revise si su aplicación está registrando una instancia en el SDK de Android pero no la está lanzando. El registro de un oyente en un evento de bus también podría ser una posible filtración.
Descargo de responsabilidad: la herramienta que he estado usando para monitorear mi aplicación está en desarrollo. Tengo acceso a esta herramienta porque soy uno de los desarrolladores :) Si quieres acceder a esta herramienta, pronto lanzaremos una versión beta. Puede unirse a nuestro sitio web: http://flowup.io/ .
Si quieres utilizar diferentes herramientas puedes utilizar: traveview, dmtracedump, systrace o el monitor de rendimiento de Andorid integrado en Android Studio. Pero recuerde que estas herramientas monitorearán su dispositivo conectado y no el resto de sus dispositivos de usuario o instalaciones del sistema operativo Android.
fuente
También es importante verificar el diseño principal en el que colocó su Recyclerview. Tuve problemas de desplazamiento similares cuando probé ReciclerView en una vista de desplazamiento anidada. Una vista que se desplaza en otra vista que el desplazamiento puede sufrir en el rendimiento durante el desplazamiento
fuente
En mi caso, descubrí que la causa notable del retraso es el
#onBindViewHolder()
método de carga interna de extracción frecuente . Lo resolví simplemente cargando las imágenes como Bitmap una vez dentro de ViewHolder y accediendo a él desde el método mencionado. Eso es todo lo que hice.fuente
En mi RecyclerView, utilizo imágenes de mapa de bits para el fondo de mi item_layout.
Todo lo que dijo @Galya es cierto (y le agradezco su gran respuesta). Pero no funcionaron para mí.
Esto es lo que resolvió mi problema:
BitmapFactory.Options options = new BitmapFactory.Options(); options.inSampleSize = 2; Bitmap bitmap = BitmapFactory.decodeStream(stream, null, options);
Para obtener más información, lea esta respuesta .
fuente
En mi caso, tengo hijos complejos de reciclaje. Entonces afectó el tiempo de carga de la actividad (~ 5 segundos para la representación de la actividad)
Cargué el adaptador con postDelayed () -> esto dará un buen resultado para la representación de la actividad. después de la actividad renderizando mi carga de reciclaje con suavidad.
Prueba esta respuesta
recyclerView.postDelayed(new Runnable() { @Override public void run() { recyclerView.setAdapter(mAdapter); } },100);
fuente
Veo en los comentarios que ya estás implementando el
ViewHolder
patrón, pero aquí publicaré un adaptador de ejemplo que usa elRecyclerView.ViewHolder
patrón para que puedas verificar que lo estás integrando de manera similar, nuevamente tu constructor puede variar dependiendo de tus necesidades, aquí es un ejemplo: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); } } }
Si tiene algún problema para trabajar,
RecyclerView.ViewHolder
asegúrese de tener las dependencias adecuadas que puede verificar siempre en Gradle.Espero que resuelva tu problema.
fuente
Esto me ayudó a obtener un desplazamiento más suave:
anular el onFailedToRecycleView (soporte de ViewHolder) en el adaptador
y detener cualquier titular de animaciones en curso (si corresponde). "animateview" .clearAnimation ();
recuerde devolver la verdad;
fuente
Lo resolví con esta línea de código
recyclerView.setNestedScrollingEnabled(false);
fuente
Agregando a la respuesta de @ Galya, en bind viewHolder, estaba usando el método Html.fromHtml (). aparentemente esto tiene un impacto en el rendimiento.
fuente
i Resuelva este problema utilizando la única línea de la biblioteca de Picasso
.ajuste()
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(); } });
fuente