Производительность прокрутки RecyclerView

Я создал пример RecyclerView, основываясь на руководстве « Создание списков и карт» . Мой адаптер имеет реализацию шаблона только для раздувания макета.

Проблема заключается в низкой производительности прокрутки. Это с RecycleView всего из 8 элементов: P

В некоторых тестах я подтвердил, что в Android L эта проблема не возникает. Но в версии KitKat наблюдается снижение производительности.

Solutions Collecting From Web of "Производительность прокрутки RecyclerView"

Недавно я столкнулся с одной и той же проблемой, так что это то, что я сделал с последней библиотекой поддержки RecyclerView:

  1. Замените сложный макет (вложенные представления, RelativeLayout) с новым оптимизированным ConstraintLayout. Активируйте его в Android Studio: перейдите в SDK Manager -> вкладка SDK Tools -> Support Repository -> проверьте ConstraintLayout для Android и Solver для ConstraintLayout. Добавить в зависимости:

    compile 'com.android.support.constraint:constraint-layout:1.0.2' 
  2. Если возможно, сделайте все элементы RecyclerView с одинаковой высотой . И добавить:

     recyclerView.setHasFixedSize(true); 
  3. Используйте методы кэширования чертежей по умолчанию RecyclerView и настройте их в соответствии с вашим случаем. Для этого вам не нужна сторонняя библиотека:

     recyclerView.setItemViewCacheSize(20); recyclerView.setDrawingCacheEnabled(true); recyclerView.setDrawingCacheQuality(View.DRAWING_CACHE_QUALITY_HIGH); 
  4. Если вы используете много изображений , убедитесь, что их размер и сжатие оптимальны . Масштабирование изображений также может повлиять на производительность. Есть две стороны проблемы: исходное изображение и декодированное растровое изображение. Следующий пример дает вам подсказку, как декодировать изображение, загруженное из Интернета:

     InputStream is = (InputStream) url.getContent(); BitmapFactory.Options options = new BitmapFactory.Options(); options.inPreferredConfig = Bitmap.Config.RGB_565; Bitmap image = BitmapFactory.decodeStream(is, null, options); 

Наиболее важной частью является указание inPreferredConfig – он определяет, сколько байтов будет использоваться для каждого пикселя изображения. Имейте в виду, что это предпочтительный вариант. Если исходное изображение имеет больше цветов, оно все равно будет декодироваться с другой конфигурацией.

  1. Убедитесь, что onBindViewHolder () как можно дешевле . Вы можете установить OnClickListener один раз в onCreateViewHolder() и вызвать через интерфейс прослушиватель вне адаптера, передав элемент, щелкнув по onCreateViewHolder() . Таким образом, вы не создаете лишние объекты все время. Также проверяйте флаги и состояния, прежде чем вносить какие-либо изменения в представление здесь.

     viewHolder.itemView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Item item = getItem(getAdapterPosition()); outsideClickListener.onItemClicked(item); } }); 
  2. Когда данные будут изменены, попробуйте обновить только затронутые элементы . Например, вместо того, чтобы недействить весь набор данных с notifyDataSetChanged() , добавляя / загружая больше элементов, просто используйте:

     adapter.notifyItemRangeInserted(rangeStart, rangeEnd); adapter.notifyItemRemoved(position); adapter.notifyItemChanged(position); adapter.notifyItemInserted(position); 
  3. С веб-сайта разработчика Android :

Положитесь на notifyDataSetChanged () в качестве последнего средства.

Но если вам нужно использовать его, сохраните свои элементы с уникальными идентификаторами :

  adapter.setHasStableIds(true); 

RecyclerView попытается синтезировать видимые события структурных изменений для адаптеров, которые сообщают, что они имеют стабильные идентификаторы при использовании этого метода. Это может помочь в целях сохранения анимации и визуального объекта, но отдельные представления элементов все равно нуждаются в отскоке и восстановлении.

Даже если вы все сделаете правильно, есть вероятность, что RecyclerView все еще работает не так гладко, как вам хотелось бы.

Я решил эту проблему, добавив следующий флаг:

https://developer.android.com/reference/android/support/v7/widget/RecyclerView.Adapter.html#setHasStableIds(boolean)

Я обнаружил по крайней мере один образец, который может убить вашу производительность. Помните, что onBindViewHolder() вызывается часто . Таким образом, все, что вы делаете в этом коде, может остановить работу. Если ваш RecyclerView выполняет какую-либо настройку, очень просто случайно поставить некоторый медленный код в этом методе.

Я изменил фоновое изображение каждого RecyclerView в зависимости от позиции. Но загрузка изображений требует немного работы, в результате чего мой RecyclerView будет вялым и отрывистым.

Создание кеша для изображений сработало чудесами; onBindViewHolder() теперь просто изменяет ссылку на кешированное изображение, а не загружает его с нуля. Теперь RecyclerView застегивается.

Я знаю, что не у всех будет эта точная проблема, поэтому я не буду загружать код. Но, пожалуйста, рассмотрите любую работу, которая выполняется в вашем onBindViewHolder() в качестве потенциальной бутылочной шейки для плохой производительности RecyclerView.

Я не уверен, будет ли использование флага setHasStableId исправлять вашу проблему. Основываясь на информации, которую вы предоставляете, проблема с производительностью может быть связана с проблемой памяти. Производительность вашего приложения с точки зрения пользовательского интерфейса и памяти весьма взаимосвязана.

На прошлой неделе я обнаружил, что в моем приложении происходит утечка памяти. Я обнаружил это, потому что через 20 минут, используя мое приложение, я заметил, что пользовательский интерфейс работает очень медленно. Закрытие / открытие активности или прокрутка RecyclerView с кучей элементов были очень медленными. После мониторинга некоторых моих пользователей в производстве, используя http://flowup.io/, я нашел следующее:

Введите описание изображения здесь

Время кадра было действительно очень высоким, и кадры в секунду действительно были очень низкими. Вы можете видеть, что для некоторых кадров требуется около 2 секунд: S.

Попытка выяснить, что вызвало это вредное фрейм / fps, я обнаружил, что у меня была проблема с памятью, как вы можете видеть здесь:

Введите описание изображения здесь

Даже когда среднее потребление памяти было близко к 15 МБ, в то же время приложение уменьшало кадры.

Вот как я открыл проблему с пользовательским интерфейсом. У меня была утечка памяти в моем приложении, вызвавшая множество событий сборщика мусора, и это вызывало плохую производительность пользовательского интерфейса, потому что Android VM пришлось остановить мое приложение, чтобы собрать память на каждый отдельный кадр.

Глядя на код, у меня была утечка внутри пользовательского представления, потому что я не регистрировал слушателя из экземпляра Android хореографа. После выпуска исправления все стало нормально 🙂

Если ваше приложение удаляет фреймы из-за проблемы с памятью, вы должны рассмотреть две распространенные ошибки:

Просмотрите, если ваше приложение выделяет объекты внутри метода, вызываемого несколько раз в секунду. Даже если это распределение может быть выполнено в другом месте, где ваше приложение становится медленным. Примером может быть создание новых экземпляров объекта внутри настраиваемого метода просмотра onDraw на onBindViewHolder в держателе вида просмотра ресайклеров. Просмотрите, зарегистрировано ли приложение в Android SDK, но не освобождает его. Регистрация прослушивателя в событии шины также может быть возможной утечкой.

Отказ от ответственности: инструмент, который я использовал для мониторинга моего приложения, находится в разработке. У меня есть доступ к этому инструменту, потому что я один из разработчиков 🙂 Если вы хотите получить доступ к этому инструменту, мы скоро выпустим бета-версию! Вы можете присоединиться к нашему веб-сайту: http://flowup.io/ .

Если вы хотите использовать разные инструменты, вы можете использовать: traveview, dmtracedump, systrace или монитор производительности Andorid, интегрированный в Android Studio. Но помните, что эти инструменты будут контролировать ваше подключенное устройство, а не остальные ваши пользовательские устройства или установки ОС Android.

Я вижу в комментариях, что вы уже реализуете шаблон ViewHolder , но я ViewHolder здесь пример адаптера, который использует шаблон RecyclerView.ViewHolder , чтобы вы могли убедиться, что вы его интегрируете аналогичным образом, снова ваш конструктор может варьироваться в зависимости от Ваши потребности, вот пример:

 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); } } } 

Если у вас возникли проблемы с RecyclerView.ViewHolder убедитесь, что у вас есть соответствующие зависимости, которые вы всегда можете проверить в Gradle Please

Надеюсь, он решает вашу проблему.

В моем случае я обнаружил, что заметной причиной задержки является частая возможность загрузки внутри #onBindViewHolder() . Я решил это, просто загрузив изображения как Bitmap один раз в ViewHolder и получив доступ к этому методу. Это все, что я сделал.