java android performance android-recyclerview recycler-adapter

java - RecyclerView Rendimiento de desplazamiento



android performance (14)

He creado el ejemplo RecyclerView basándose 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 bajo rendimiento de desplazamiento. Esto en un RecycleView con solo 8 elementos.

En algunas pruebas verifiqué que en Android L este problema no ocurre. Pero en la versión KitKat, la disminución del rendimiento es evidente.


Además de la respuesta detallada de @ Galya, quiero decir que, aunque puede ser un problema de optimización, también es cierto que tener habilitado el depurador puede ralentizar mucho las cosas.

Si hace todo lo posible para optimizar su RecyclerView y todavía no funciona sin problemas, intente cambiar su variante de compilación para su release y verifique cómo funciona en un entorno sin desarrollo (con el depurador deshabilitado).

Me sucedió que mi aplicación funcionaba lentamente en la variante de compilación de debug , pero tan pronto como cambié a la variante de release funcionó sin problemas. Esto no significa que deba desarrollar con la variante de compilación de release , pero es bueno saber que siempre que esté listo para enviar su aplicación, funcionará bien.


Agregando a la respuesta de @ Galya, en bind viewHolder, estaba usando el método Html.fromHtml (). aparentemente esto tiene impacto en el rendimiento.


Descubrí al menos un patrón que puede matar tu rendimiento. Recuerde que onBindViewHolder() se llama con frecuencia . Entonces, cualquier cosa que haga en ese código tiene el potencial de detener su rendimiento. Si su RecyclerView realiza alguna personalización, es muy fácil colocar accidentalmente un código lento en este método.

Estaba cambiando las imágenes de fondo de cada RecyclerView dependiendo de la posición. Pero cargar imágenes requiere un poco de trabajo, lo que hace que mi RecyclerView sea lenta y desigual.

Crear 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 se desliza a lo largo.

Sé que no todos tendrán este problema exacto, así que no me molesto en cargar el código. Pero considere cualquier trabajo que se realice en su onBindViewHolder() como un cuello de botella potencial para un bajo rendimiento de RecyclerView.


En mi RecyclerView, uso 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 .


En mi caso, descubrí que la causa notable del retraso es la carga de #onBindViewHolder() frecuente dentro del método #onBindViewHolder() . 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.


En mi caso, tengo niños reciclados complejos. Entonces afectó el tiempo de carga de la actividad (~ 5 segundos para la representación de la actividad)

Cargo el adaptador con postDelayed () -> esto dará el buen resultado para la representación de la actividad. después de la actividad haciendo que mi vista de reciclado se cargue sin problemas.

Prueba esta respuesta,

recyclerView.postDelayed(new Runnable() { @Override public void run() { recyclerView.setAdapter(mAdapter); } },100);


Esto me ayudó a obtener un desplazamiento más suave:

anular el onFailedToRecycleView (soporte ViewHolder) en el adaptador

y detener cualquier titular de animaciones en curso (si corresponde) "animateview" .clearAnimation ();

recuerda volver verdadero;


Lo resolví por esta línea de código

recyclerView.setNestedScrollingEnabled(false);


No estoy realmente seguro de si el uso del indicador setHasStableId va a 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 lentamente. Cerrar / abrir una actividad o desplazar 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 marcos necesitaban unos 2 segundos para renderizarse: S.

Tratando de averiguar qué estaba causando este mal tiempo de fotogramas / fps, descubrí que tenía un problema de memoria, como puede ver aquí:

Incluso cuando el consumo promedio de memoria estaba cerca de los 15 MB al mismo tiempo, la aplicación estaba perdiendo fotogramas.

Así es como descubrí el problema de la interfaz de usuario. Tuve una pérdida de memoria en mi aplicación causando muchos eventos de recolección de basura y eso estaba causando el mal rendimiento de la interfaz de usuario porque la VM de Android tuvo que detener mi aplicación para recopilar memoria en cada cuadro.

Al mirar el código, tuve una fuga dentro de una vista personalizada porque no anulaba 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 está asignando 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 personalizada onDraw en onBindViewHolder en el soporte de la vista de la vista del reciclador. Revise si su aplicación está registrando una instancia en el SDK de Android pero no la está lanzando. Registrar un oyente en un evento de autobús también podría ser una posible fuga.

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, lanzaremos una versión beta pronto! Puede unirse a nuestro sitio web: http://flowup.io/ .

Si desea utilizar diferentes herramientas, puede usar: traveview, dmtracedump, systrace o el monitor de rendimiento de Andorid integrado en Android Studio. Pero recuerde que estas herramientas supervisarán su dispositivo conectado y no el resto de sus dispositivos de usuario o instalaciones del sistema operativo Android.


Recientemente me he enfrentado al mismo problema, así que esto es lo que hice con la última biblioteca de soporte RecyclerView:

  1. Reemplace un diseño complejo (vistas anidadas, RelativeLayout) con el nuevo ConstraintLayout optimizado. Actívelo en Android Studio: vaya al Administrador de SDK -> pestaña Herramientas de SDK -> Repositorio de soporte -> marque ConstraintLayout para Android y Solver para ConstraintLayout. Añadir a las dependencias:

    compile ''com.android.support.constraint:constraint-layout:1.0.2''

  2. Si es posible, cree todos los elementos de RecyclerView con la misma altura . Y añadir:

    recyclerView.setHasFixedSize(true);

  3. Utilice los métodos de caché de dibujo RecyclerView predeterminados y modifíquelos 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);

  4. Si usa muchas imágenes , asegúrese de que su tamaño y compresión sean óptimos . Escalar 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 usará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, seguirá decodificándose con una configuración diferente.

  1. Asegúrese de que onBindViewHolder () sea ​​lo más barato posible. Puede configurar OnClickListener una vez en 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 hacer cualquier cambio en la vista aquí.

    viewHolder.itemView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Item item = getItem(getAdapterPosition()); outsideClickListener.onItemClicked(item); } });

  2. 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:

    adapter.notifyItemRangeInserted(rangeStart, rangeEnd); adapter.notifyItemRemoved(position); adapter.notifyItemChanged(position); adapter.notifyItemInserted(position);

  3. Desde el sitio web para desarrolladores de Android :

Confíe en notifyDataSetChanged () como último recurso.

Pero si necesita usarlo, mantenga sus artículos con identificadores únicos :

adapter.setHasStableIds(true);

RecyclerView intentará sintetizar eventos de cambio estructural visibles para los adaptadores que informan que tienen ID estables cuando se utiliza este método. Esto puede ayudar a los fines de la animación y la persistencia de los objetos visuales, pero las vistas de elementos individuales aún tendrán que recuperarse y volver a transmitirse.

Incluso si hace todo bien, lo más probable es que RecyclerView todavía no funcione tan bien como le gustaría.



También es importante verificar el diseño principal en el que coloca en su Recyclerview. Tuve un problema de desplazamiento similar cuando probé recyclerView 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


Tuve una charla sobre el rendimiento de RecyclerView . Aquí hay diapositivas en inglés y videos grabados 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 elementos del Adapter tienen un tamaño fijo, configure:
    recyclerView.setHasFixedSize(true);

  • Si las entidades de datos se pueden representar con long ( hashCode() por ejemplo), establezca:
    adapter.hasStableIds(true);
    e implementar:
    // YourAdapter.java
    @Override
    public long getItemId(int position) {
    return items.get(position).hashcode(); //id()
    }
    En este caso, Item.id() no funcionaría, ya que se mantendría igual incluso si el contenido de Item ha cambiado.
    PD: ¡Esto no es necesario si está usando DiffUtil!

  • Utilice un mapa de bits correctamente escalado. No reinvente la rueda y use bibliotecas.
    Más información sobre cómo elegir here .

  • Utilice siempre la última versión de RecyclerView . Por ejemplo, hubo grandes mejoras de rendimiento en 25.1.0 : 25.1.0 .
    Más información here .

  • Use DiffUtill.
    DiffUtil es imprescindible .
    Documentación oficial

  • ¡Simplifica el diseño de tu artículo!
    Pequeña biblioteca para enriquecer TextViews - TextViewRichDrawable

Ver diapositivas para una explicación más detallada.


Veo en los comentarios que ya está implementando el patrón ViewHolder , pero publicaré un adaptador de ejemplo aquí que usa el patrón RecyclerView.ViewHolder para que pueda verificar que lo está integrando de manera similar, nuevamente su constructor puede variar dependiendo de sus necesidades, aquí hay 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 problemas para trabajar con RecyclerView.ViewHolder asegúrese de tener las dependencias apropiadas que puede verificar siempre en Gradle Please

Espero que resuelva tu problema.