Производительность прокрутки RecyclerView
Я создал пример RecyclerView на основе руководства по созданию списков и карт. Мой адаптер имеет реализацию шаблона только для раздувания макета.
Проблема в плохой прокрутке. Это в RecycleView только с 8 элементами.
В некоторых тестах я проверял, что в Android L такой проблемы не возникает. Но в версии KitKat снижение производительности очевидно.
Ответы
Ответ 1
Недавно я столкнулся с одной и той же проблемой, поэтому это то, что я сделал с последней библиотекой поддержки RecyclerView:
-
Заменить сложный макет (вложенные представления, RelativeLayout) с новым оптимизированным ConstraintLayout. Активируйте его в Android Studio: зайдите в SDK Manager → вкладка SDK Tools → Support Repository → проверьте ConstraintLayout для Android и Solver для ConstraintLayout. Добавьте в зависимости:
compile 'com.android.support.constraint:constraint-layout:1.0.2'
-
Если возможно, сделайте все элементы RecyclerView с той же высотой. И добавьте:
recyclerView.setHasFixedSize(true);
-
Используйте методы RecyclerView кэш по умолчанию и настройте их в соответствии с вашим случаем. Для этого вам не нужна сторонняя библиотека:
recyclerView.setItemViewCacheSize(20);
recyclerView.setDrawingCacheEnabled(true);
recyclerView.setDrawingCacheQuality(View.DRAWING_CACHE_QUALITY_HIGH);
-
Если вы используете много изображений, убедитесь, что их размер и сжатие оптимальны. Масштабирование изображений также может повлиять на производительность. Есть две стороны проблемы - исходное изображение и декодированное растровое изображение. Следующий пример дает вам подсказку, как декодировать изображение, загруженное из Интернета:
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
- она определяет, сколько байтов будет использоваться для каждого пикселя изображения. Имейте в виду, что это предпочтительный вариант. Если исходное изображение имеет больше цветов, оно все равно будет декодироваться с другой конфигурацией.
-
Убедитесь, что onBindViewHolder() максимально дешево. Вы можете установить OnClickListener один раз в onCreateViewHolder()
и вызвать через интерфейс прослушиватель вне адаптера, передав элемент, щелкнув по нему. Таким образом, вы не создаете лишние объекты все время. Также проверяйте флаги и состояния перед внесением изменений в представление здесь.
viewHolder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Item item = getItem(getAdapterPosition());
outsideClickListener.onItemClicked(item);
}
});
-
Когда данные будут изменены, попробуйте обновить только затронутые элементы. Например, вместо того, чтобы недействить весь набор данных с помощью notifyDataSetChanged()
, добавляя/загружая больше элементов, просто используйте:
adapter.notifyItemRangeInserted(rangeStart, rangeEnd);
adapter.notifyItemRemoved(position);
adapter.notifyItemChanged(position);
adapter.notifyItemInserted(position);
-
От Веб-сайт разработчика Android:
Положитесь на notifyDataSetChanged() в качестве последнего средства.
Но если вам нужно использовать его, сохраните свои элементы с помощью уникальных идентификаторов:
adapter.setHasStableIds(true);
RecyclerView попытается синтезировать видимые структурные изменения события для адаптеров, которые сообщают, что они имеют стабильные идентификаторы, когда это метод используется. Это может помочь в целях анимации и визуального сохранение объектов, но отдельные представления элементов все равно должны быть отскок и релаксация.
Даже если вы все сделаете правильно, возможно, что RecyclerView по-прежнему не работает так гладко, как вам хотелось бы.
Ответ 2
Я решил эту проблему, добавив следующий флаг:
https://developer.android.com/reference/android/support/v7/widget/RecyclerView.Adapter.html#setHasStableIds(boolean)
Ответ 3
Я обнаружил по крайней мере один шаблон, который может убить вашу производительность. Помните, что onBindViewHolder()
часто называют . Таким образом, все, что вы делаете в этом коде, может помешать вашей работе остановиться. Если ваш RecyclerView выполняет какую-либо настройку, очень легко случайно поставить некоторый медленный код в этом методе.
Я менял фоновые изображения каждого RecyclerView в зависимости от позиции. Но загрузка изображений требует немного работы, в результате чего мой RecyclerView будет вялым и отрывистым.
Создание кеша для изображений, сработавших чудесами; onBindViewHolder()
теперь просто изменяет ссылку на кешированное изображение, а не загружает его с нуля. Теперь RecyclerView застегивается вперед.
Я знаю, что не у всех будет эта точная проблема, поэтому я не буду загружать код. Но, пожалуйста, рассмотрите любую работу, выполненную в onBindViewHolder()
как потенциальную бутылочную горловину для плохой работы RecyclerView.
Ответ 4
В дополнение к подробному ответу @Galya, я хочу сказать, что, хотя это может быть проблемой оптимизации, верно и то, что включение отладчика может сильно замедлить процесс.
Если вы делаете все, чтобы оптимизировать RecyclerView
и он все еще не работает гладко, попробуйте переключить ваш вариант сборки на release
и проверить, как он работает в среде без разработки (с отключенным отладчиком).
Со мной случилось, что мое приложение работало медленно в варианте debug
сборки, но как только я переключился на вариант release
оно работало гладко. Это не означает, что вы должны разрабатывать с вариантом сборки release
, но полезно знать, что когда вы будете готовы к отправке приложения, оно будет работать просто отлично.
Ответ 5
Я не уверен, будет ли использование флага setHasStableId
исправить вашу проблему. Основываясь на информации, которую вы предоставляете, проблема с производительностью может быть связана с проблемой памяти. Производительность вашего приложения с точки зрения пользовательского интерфейса и памяти очень близка.
На прошлой неделе я обнаружил, что в моем приложении происходит утечка памяти. Я обнаружил это, потому что через 20 минут, используя мое приложение, я заметил, что пользовательский интерфейс работает очень медленно. Закрытие/открытие активности или прокрутка RecyclerView с кучей элементов были очень медленными. После мониторинга некоторых из моих пользователей в производстве, используя http://flowup.io/, я нашел это:
![введите описание изображения здесь]()
Время кадра было действительно очень высоким, а кадры в секунду действительно очень низкие. Вы можете видеть, что некоторым кадрам требуется около 2 секунд для рендеринга: S.
Попытка выяснить, что вызывало это вредное фрейм /fps, я обнаружил, что у меня была проблема с памятью, как вы можете видеть здесь:
![введите описание изображения здесь]()
Даже когда среднее потребление памяти было близко к 15 МБ, в то же время приложение удаляло кадры.
Как я обнаружил проблему с пользовательским интерфейсом. У меня была утечка памяти в моем приложении, вызвавшая много событий сборщика мусора, и это приводило к плохой производительности пользовательского интерфейса, потому что Android VM пришлось остановить мое приложение, чтобы собирать память в каждом отдельном фрейме.
Глядя на код, у меня была утечка внутри пользовательского представления, потому что я не регистрировал слушателя из экземпляра Android Choreographer. После освобождения исправления все стало нормально:)
Если ваше приложение удаляет фреймы из-за проблемы с памятью, вы должны рассмотреть две распространенные ошибки:
Просмотрите, будет ли ваше приложение распределять объекты внутри метода, вызываемого несколько раз в секунду. Даже если это распределение можно выполнить в другом месте, где ваше приложение становится медленным. Примером может быть создание новых экземпляров объекта внутри встроенного метода onDraw для onBindViewHolder в вашем держателе вида просмотра ресайклеров.
Просмотрите, зарегистрировано ли приложение в Android SDK, но не выпущено. Регистрация прослушивателя в событии шины также может быть возможной утечкой.
Отказ от ответственности: инструмент, который я использовал для мониторинга моего приложения, находится в разработке. У меня есть доступ к этому инструменту, потому что я один из разработчиков:) Если вы хотите получить доступ к этому инструменту, мы скоро выпустим бета-версию! Вы можете присоединиться к нашему веб-сайту: http://flowup.io/.
Если вы хотите использовать разные инструменты, вы можете использовать: traveview, dmtracedump, systrace или монитор производительности Andorid, интегрированный в Android Studio. Но помните, что эти инструменты будут контролировать ваше подключенное устройство, а не остальные ваши пользовательские устройства или установки ОС Android.
Ответ 6
Я говорил о производительности RecyclerView
. Ниже приведены слайды на английском языке и записанное видео на русском языке.
Он содержит набор методов (некоторые из них уже покрыты ответом @Дарьи).
Вот краткое резюме:
-
Если элементы Adapter
имеют фиксированный размер, установите:
recyclerView.setHasFixedSize(true);
-
Если объекты данных могут быть представлены длинными (например, hashCode()
), то установите:
adapter.hasStableIds(true);
и реализовать:
// YourAdapter.java
@Override
public long getItemId(int position) {
return items.get(position).hashcode(); //id()
}
В этом случае Item.id()
не будет работать, потому что он остался бы таким же, даже если содержимое Item
изменилось.
P.S. Это не обязательно, если вы используете DiffUtil!
-
Используйте правильно масштабированное растровое изображение. Не изобретайте велосипед и не используйте библиотеки.
Подробнее о том, как выбрать здесь.
-
Всегда используйте последнюю версию RecyclerView
. Например, в 25.1.0
- предварительной выборке произошли большие улучшения производительности.
Подробнее здесь.
-
Использовать DiffUtill.
DiffUtil является обязательным.
Официальная документация.
-
Упростите расположение вашего макета!
Крошечная библиотека для обогащения TextViews - TextViewRichDrawable
Подробнее см. слайды.
Ответ 7
В моем случае я обнаружил, что заметной причиной задержки является частая возможность загрузки внутри метода #onBindViewHolder()
. Я решил это, просто загрузив изображения как Растровое изображение один раз внутри ViewHolder и получив доступ к нему из указанного метода. Это все, что я сделал.
Ответ 8
Я вижу в комментариях, что вы уже реализуете шаблон 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 Пожалуйста,
Надеюсь, что он решает вашу проблему.
Ответ 9
Это помогло мне получить более плавную прокрутку:
переопределить onFailedToRecycleView (держатель ViewHolder) в адаптере
и прекратить любую текущую анимацию (если есть) Держатель "animateview".clearAnimation();.
помню
return true;
Ответ 10
В моем RecyclerView я использую растровые изображения для фона моего item_layout.
Все, что сказал Галя, верно (и я благодарю его за его большой ответ). Но они не работали для меня.
Вот что решило мою проблему:
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 2;
Bitmap bitmap = BitmapFactory.decodeStream(stream, null, options);
За дополнительной информацией, пожалуйста, прочитайте
этот ответ.
Ответ 11
В моем случае у меня сложный реселлер, вид ребенка. Так что это повлияло на время загрузки активности (~ 5 сек для рендеринга активности)
Я загружаю адаптер с помощью postDelayed() → это даст хороший результат для рендеринга активности. после рендеринга активности мой просмотрщик загрузился с гладкой.
Попробуйте этот ответ,
recyclerView.postDelayed(new Runnable() {
@Override
public void run() {
recyclerView.setAdapter(mAdapter);
}
},100);
Ответ 12
Также важно проверить родительский макет, в который вы добавили свое Recyclerview. У меня были похожие проблемы с прокруткой при тестировании recyclerView в nestedscrollview. Представление, которое прокручивает в другом представлении, что прокрутка может пострадать в производительности во время прокрутки
Ответ 13
Я решил это с помощью этой строки кода
recyclerView.setNestedScrollingEnabled(false);
Ответ 14
Добавляя к ответу @Galya, в bind viewHolder я использовал метод Html.fromHtml(). по-видимому, это влияет на производительность.