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

86

Я создал пример RecyclerView на основе руководства по созданию списков и карточек . У моего адаптера есть реализация паттерна только для надувания макета.

Проблема в плохой скорости прокрутки. Это в RecycleView всего с 8 элементами.

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

Falvojr
источник
1
Попробуйте использовать шаблон проектирования ViewHolder для повышения производительности прокрутки: developer.android.com/training/improving-layouts/…
Haresh Chhelana
@HareshChhelana спасибо за ответ! Но я уже использую паттерн ViewHolder, по ссылке: developer.android.com/training/material/lists-cards.html
falvojr
2
Не могли бы вы поделиться кодом настройки вашего адаптера и XML-файлом для ваших макетов. Это не выглядит нормально. Кроме того, вы профилировали и видели, где вы проводите время?
yigit
2
У меня почти одинаковые проблемы. За исключением того, что он был быстрым в Pre Lollipop и невероятно (действительно) медленно в Android L.
Servus7
1
Можете ли вы также поделиться версией импортируемой библиотеки.
Дроидекас

Ответы:

227

Недавно я столкнулся с той же проблемой, поэтому вот что я сделал с последней библиотекой поддержки 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()и вызвать через интерфейс прослушиватель вне адаптера, передавая выбранный элемент. Таким образом, вы не будете постоянно создавать лишние объекты. Также проверьте флаги и состояния, прежде чем вносить какие-либо изменения в представление здесь.

    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 по-прежнему работает не так гладко, как хотелось бы.

Галя
источник
18
один голос за adapter.setHasStableIds (true); метод, который действительно помог сделать recyclerview быстрым.
Атула
1
Часть 7 совершенно неверна! setHasStableIds (true) ничего не сделает, кроме использования adapter.notifyDataSetChanged (). Ссылка: developer.android.com/reference/android/support/v7/widget/…
localhost,
1
Я понимаю, почему это recyclerView.setItemViewCacheSize(20);может улучшить производительность. Но recyclerView.setDrawingCacheEnabled(true);и recyclerView.setDrawingCacheQuality(View.DRAWING_CACHE_QUALITY_HIGH);хоть! Я не уверен, что это что-то изменит. Это Viewособые вызовы, которые позволяют программно получать кэш чертежа в виде растрового изображения и использовать его в дальнейшем в своих интересах. RecyclerViewпохоже, ничего не делает с этим.
Абдельхаким АКОДАДИ
2
@AbdelhakimAkodadi, с кешем прокрутка становится плавной. Я это тестировал. Как можно было иначе сказать, это очевидно. Конечно, если кто-то как сумасшедший будет листать, ничего не поможет. Я просто показываю другие параметры, такие как setDrawingCacheQuality, которые я не использую, потому что в моем случае важно качество изображения. Я не проповедую DRAWING_CACHE_QUALI‌ TY_HIGH, но предлагаю всем, кто заинтересован, вникнуть глубже и настроить опцию.
Галя
2
setDrawingCacheEnabled () и setDrawingCacheQuality () устарели. Вместо этого используйте аппаратное ускорение. developer.android.com/reference/android/view/…
Shayan_Aryan
14

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

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

Waclaw
источник
2
Это дает мне небольшое улучшение производительности. Но RecyclerView по-прежнему очень медленный - намного медленнее, чем эквивалентный настраиваемый ListView.
SMBiggs
Ах, но я обнаружил, почему мой код такой медленный - он не имеет ничего общего с setHasStableIds (). Я отправлю ответ с дополнительной информацией.
SMBiggs
13

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

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

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

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

SMBiggs
источник
У меня такая же проблема. Сейчас я использую Fresco для загрузки и кеширования изображений. У вас есть другое, лучшее решение для загрузки и кеширования изображений в RecyclerView. Спасибо.
Androidicus
Я не знаком с Fresco (читаю ... их обещания хороши). Возможно, у них есть представление о том, как лучше всего использовать свои кеши с RecyclerViews. И я вижу, что не только вы столкнулись с этой проблемой: github.com/facebook/fresco/issues/414 .
SMBiggs
10

В дополнение к подробному ответу @Galya я хочу заявить, что, хотя это может быть проблема с оптимизацией, также верно и то, что включение отладчика может сильно замедлить работу.

Если вы делаете все, чтобы оптимизировать свой, RecyclerViewно он по-прежнему не работает гладко, попробуйте переключить свой вариант сборки на releaseи проверьте, как он работает в среде, не связанной с разработкой (с отключенным отладчиком).

Со мной случилось так, что мое приложение работало медленно в debugварианте сборки, но как только я переключился на releaseвариант, оно работало плавно. Это не означает, что вам следует разрабатывать releaseвариант сборки, но полезно знать, что всякий раз, когда вы будете готовы отправить свое приложение, оно будет работать нормально.

бластервла
источник
Этот комментарий был мне очень полезен! Я пробовал все, чтобы улучшить производительность моего recyclerview, но ничего не помогло, но как только я переключился на сборку релиза, я понял, что все в порядке.
Тал Барда
1
Я столкнулся с той же проблемой даже без прикрепленного отладчика, но как только перешел к выпуску сборки, проблемы больше не было видно
Фармаан Элахи
8

Я говорил о RecyclerViewспектакле России. Здесь представлены слайды на английском языке и записанное видео на русском языке .

Он содержит набор приемов (некоторые из них уже описаны в ответе @Darya ).

Вот краткое изложение:

  • Если Adapterэлементы имеют фиксированный размер, установите:
    recyclerView.setHasFixedSize(true);

  • Если объекты данных могут быть представлены как long ( hashCode()например), тогда установите:
    adapter.hasStableIds(true);
    и выполните:
    // YourAdapter.java
    @Override
    public long getItemId(int position) {
    return items.get(position).hashcode(); //id()
    }
    В этом случае Item.id()не будет работать, потому что он останется таким же, даже если Itemсодержимое изменилось.
    PS В этом нет необходимости, если вы используете DiffUtil!

  • Используйте правильно масштабированное растровое изображение. Не изобретайте велосипед и не используйте библиотеки.
    Подробнее как выбрать здесь .

  • Всегда используйте последнюю версию RecyclerView. Например, значительно улучшена производительность 25.1.0- prefetch.
    Больше информации здесь .

  • Используйте DiffUtill.
    DiffUtil просто необходим .
    Официальная документация .

  • Упростите макет вашего предмета!
    Маленькая библиотека для обогащения TextViews - TextViewRichDrawable

Смотрите слайды для более подробного объяснения.

Александр
источник
7

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

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

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

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

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

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

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

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

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

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

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

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

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

Педро Висенте Гомес Санчес
источник
3

Также важно проверить родительский макет, в который вы помещаете Recyclerview. У меня были аналогичные проблемы с прокруткой, когда я тестировал recyclerView во nestedscrollview. Представление, которое прокручивается в другом представлении, при прокрутке может снижаться производительность при прокрутке

Saintjab
источник
Да действительно точка. Я тоже сталкиваюсь с той же проблемой!
Ранджит Кумар
2

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

Вэй Чан
источник
2

В моем RecyclerView я использую растровые изображения для фона моего item_layout.
Все, что сказал @Galya, правда (и я благодарю его за отличный ответ). Но у меня они не работали.

Вот что решило мою проблему:

BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 2;
Bitmap bitmap = BitmapFactory.decodeStream(stream, null, options);

Для получения дополнительной информации прочтите этот ответ .

mohandesR
источник
2

В моем случае у меня есть сложные дочерние элементы recyclerview. Это повлияло на время загрузки активности (~ 5 секунд для рендеринга активности)

Я загружаю адаптер с помощью postDelayed () -> это даст хороший результат для рендеринга активности. после активности, отображающей мою загрузку recyclerview плавно.

Попробуйте этот ответ,

    recyclerView.postDelayed(new Runnable() {
        @Override
        public void run() {
            recyclerView.setAdapter(mAdapter);
        }
    },100); 
Ранджит Кумар
источник
1

В комментариях я вижу, что вы уже реализуете 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.

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

Джоэл
источник
1

Это помогло мне получить более плавную прокрутку:

переопределить onFailedToRecycleView (держатель ViewHolder) в адаптере

и остановить все текущие анимации (если есть). "animateview" .clearAnimation ();

не забудьте вернуть истину;

Рев Грёнмо
источник
1

Я решил это с помощью этой строки кода

recyclerView.setNestedScrollingEnabled(false);
Эли
источник
1

Добавляя к ответу @Galya, в привязке viewHolder я использовал метод Html.fromHtml (). очевидно, это влияет на производительность.

Сами Адам
источник
0

Я решил эту проблему, используя только одну строку в библиотеке Пикассо

.поместиться()

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();
                        }
                    });
Хамза Независимо
источник