Подтвердить что ты не робот

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

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

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

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

4b9b3361

Ответ 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 - она ​​определяет, сколько байтов будет использоваться для каждого пикселя изображения. Имейте в виду, что это предпочтительный вариант. Если исходное изображение имеет больше цветов, оно все равно будет декодироваться с другой конфигурацией.

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

Ответ 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(). по-видимому, это влияет на производительность.