it-swarm.com.ru

RecyclerView Scrolling Performance

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

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

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

49
falvojr

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

  1. Заменить сложный макет (вложенные представления, RelativeLayout) с новым оптимизированным ConstraintLayout. Активируйте его в Android Studio: Перейдите в Менеджер SDK -> вкладка Инструменты SDK -> Репозиторий поддержки -> проверьте 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. Если вы используете много images, убедитесь, что их размер и сжатие оптимальны. Масштабирование изображений также может повлиять на производительность. Есть две стороны проблемы - используемое исходное изображение и декодированное растровое изображение. Следующий пример дает вам подсказку, как декодировать изображение, загруженное из Интернета:

    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. From Веб-сайт разработчика Android :

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

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

    adapter.setHasStableIds(true);

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

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

145
Galya

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

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

12
waclaw

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

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

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

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

9
Scott Biggs

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

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

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

7
blastervla

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

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

 enter image description here

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

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

 enter image description here

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

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

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

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

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

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

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

5

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

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

Вот краткое резюме:

  • Если элементы 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 изменилось.
    P.S. Это не обязательно, если вы используете DiffUtil!

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

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

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

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

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

5
Oleksandr

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

2
saintjab

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

2
Chan Teck Wei

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

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

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

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

1
Roar Grønmo

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

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

1
Joel

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

1
Sami Adam

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

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

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

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

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

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

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

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

1
mohandes

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

recyclerView.setNestedScrollingEnabled(false);
0
eLi