Ефективність прокрутки Android RecyclerView


86

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

Проблема полягає в поганій продуктивності прокрутки. Це в RecycleView лише з 8 елементами.

У деяких тестах я переконався, що в Android L ця проблема не виникає. Але у версії KitKat очевидним є зниження продуктивності.


1
Спробуйте використати шаблон дизайну ViewHolder для продуктивності прокрутки: developer.android.com/training/improving-layouts/…
Хареш Челана

@HareshChhelana дякую за відповідь! Але я вже використовую шаблон ViewHolder, за посиланням: developer.android.com/training/material/lists-cards.html
falvojr

2
Чи можете ви поділитися кодом про налаштування адаптера та файлом XML для своїх макетів. Це не виглядає нормально. Крім того, ви профілювали і бачили, де витрачається час?
yigit

2
Я стикаюся з усіма тими ж проблемами. За винятком його швидкого у попередньому льодянику та неймовірного (справді) повільного в Android L.
Servus7,

1
Ви також можете поділитися версією бібліотеки, яку імпортуєте.
Droidekas

Відповіді:


227

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

  1. Замініть складний макет (вкладені подання, RelativeLayout) новим оптимізованим ConstraintLayout. Активуйте його в Android Studio: Перейдіть до менеджера SDK -> вкладка SDK Tools -> Repository Support -> перевірте 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); метод, який справді допоміг швидко зробити перегляд вторинної переробки.
Атула,

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здається, нічого з цим не робить.
Абдельхакім АКОДАДІ

1
@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)


2
Це незначно покращує продуктивність для мене. Але RecyclerView все ще працює повільно - набагато повільніше, ніж еквівалентний користувацький ListView.
SMBiggs

Ага, але я виявив, чому мій код такий повільний - він не має нічого спільного з setHasStableIds (). Я розміщу відповідь із додатковою інформацією.
SMBiggs

13

Я виявив принаймні один шаблон, який може вбити вашу ефективність. Пам'ятайте, що onBindViewHolder()дзвонить часто . Отже, все, що ви робите в цьому коді, може зупинити вашу ефективність. Якщо ваш RecyclerView виконує будь-які настройки, дуже легко випадково ввести в цей метод повільний код.

Я міняв фонові зображення кожного RecyclerView залежно від положення. Але завантаження зображень вимагає невеликої роботи, через що мій RecyclerView стає млявим і в'язким.

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

Я знаю, що не у всіх виникне саме така проблема, тому я не заважаю завантажувати код. Але, будь ласка, розгляньте будь-яку роботу, яка виконується у вас, onBindViewHolder()як потенційну горловину для поганої роботи RecyclerView.


У мене така сама проблема. Зараз я використовую фреску для завантаження та кешування зображень. У вас є інше, краще рішення для завантаження та кешування зображень у RecyclerView. Дякую.
Androidicus

Я не знайомий з Фрескою (читаю ... їхні обіцянки приємні). Можливо, вони мають деяку інформацію про те, як найкраще використовувати свої кеші за допомогою RecyclerViews. І я бачу, що ви не єдина з цією проблемою: github.com/facebook/fresco/issues/414 .
SMBiggs

10

Окрім детальної відповіді @ Galya, я хочу сказати, що, хоча це може бути проблемою оптимізації, це правда, що ввімкнення налагоджувача може значно уповільнити ситуацію.

Якщо ви робите все, щоб оптимізувати ваш, RecyclerViewале він все ще не працює гладко, спробуйте переключити свій варіант збірки на releaseі перевірте, як він працює в середовищі, що не розробляється (з відключеним налагоджувачем).

Мені траплялося, що мій додаток працював повільно у debugваріанті збірки, але як тільки я перейшов на releaseваріант, він працював безперебійно. Це не означає, що вам слід розробляти з releaseваріантом збірки, але добре знати, що всякий раз, коли ви готові надіслати свій додаток, він буде працювати прекрасно.


Цей коментар був для мене справді корисним! Я пробував усе, щоб покращити ефективність мого переробника, але насправді нічого не допомогло, але переключившись на випуск версії, я зрозумів, що все в порядку.
Таль Барда

1
Я зіткнувся з тією ж проблемою навіть без приєднаного налагоджувача, але як тільки перейшов до випуску збірки, проблему вже не було видно
Farmaan Elahi

8

Я поговорив про RecyclerViewвиступ Росії. Ось слайди англійською мовою та записане відео російською мовою .

Він містить набір прийомів (деякі з них вже висвітлено у відповіді @ Darya ).

Ось короткий зміст:

  • Якщо 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вміст змінився.
    PS Це не потрібно, якщо ви використовуєте DiffUtil!

  • Використовуйте правильно масштабоване растрове зображення. Не винаходите колесо і не користуйтесь бібліотеками.
    Детальніше, як вибрати тут .

  • Завжди використовуйте останню версію RecyclerView. Наприклад, в системі 25.1.0попередньої вибірки відбулися значні покращення продуктивності .
    Більше інформації тут .

  • Використовуйте DiffUtill.
    DiffUtil є обов’язковим .
    Офіційна документація .

  • Спростіть макет вашого товару!
    Крихітна бібліотека для збагачення TextViews - TextViewRichDrawable

Дивіться слайди для більш детального пояснення.


7

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

Минулого тижня я виявив, що мій додаток витікає з пам’яті. Я виявив це, оскільки через 20 хвилин використання мого додатка я помітив, що інтерфейс працював дуже повільно. Закриття / відкриття дії або прокрутка RecyclerView з безліччю елементів було дуже повільним. Після моніторингу деяких моїх користувачів у виробництві за допомогою http://flowup.io/ я виявив:

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

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

Намагаючись зрозуміти, що спричиняє цей невдалий час / кадр в секунду, я виявив, що у мене проблема з пам’яттю, як ви можете бачити тут:

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

Навіть коли середнє споживання пам'яті було близько 15 Мб, у той самий час програма скидала кадри.

Так я виявив проблему з інтерфейсом користувача. У моєму додатку стався витік пам’яті, що спричинило багато подій збирача сміття, і це спричинило погану продуктивність інтерфейсу, оскільки Android VM повинен був зупинити мою програму, щоб збирати пам’ять кожен окремий кадр.

Дивлячись на код, у мене просочився користувацький вигляд, оскільки я не відмінив реєстрацію слухача з екземпляра Android Choreographer. Після випуску виправлення все стало нормально :)

Якщо ваш додаток скидає кадри через проблему з пам'яттю, слід переглянути дві типові помилки:

Перевірте, чи додаток виділяє об’єкти всередині методу, який викликається кілька разів на секунду. Навіть якщо цей розподіл можна виконати в іншому місці, де ваша заявка стає повільною. Прикладом може бути створення нових екземплярів об'єкта всередині методу нестандартного подання onDraw на onBindViewHolder у власнику подання подання утилізатора. Перевірте, чи ваш додаток реєструє екземпляр в Android SDK, але не випускає його. Реєстрація слухача в автобусній події також може бути можливим.

Застереження: Інструмент, який я використовую для моніторингу мого додатка, знаходиться в стадії розробки. Я маю доступ до цього інструменту, тому що я один з розробників :) Якщо ви хочете отримати доступ до цього інструменту, ми скоро випустимо бета-версію! Ви можете приєднатися до нашого веб-сайту: http://flowup.io/ .

Якщо ви хочете використовувати різні інструменти, які ви можете використовувати: traveview, dmtracedump, systrace або монітор продуктивності Andorid, інтегрований в Android Studio. Але пам’ятайте, що ці інструменти контролюватимуть ваш підключений пристрій, а не решту ваших користувацьких пристроїв чи інсталяцій ОС Android.


3

Важливо також перевірити батьківський макет, в якому ви розміщуєте свій Recyclerview. У мене були подібні проблеми з прокруткою, коли я тестував recyclerView у вкладеному перегляді. Вигляд, який прокручується в іншому поданні, яке прокручується, може погіршити продуктивність під час прокрутки


Так дійсний пункт. Я теж стикаюся з такою ж проблемою!
Ранджит Кумар,

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

Для отримання додаткової інформації, будь ласка, прочитайте цю відповідь .


2

У моєму випадку у мене є складні діти по переробці відходів. Отже, це вплинуло на час завантаження активності (~ 5 с для відображення активності)

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

Спробуйте цю відповідь,

    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 Please

Сподіваюся, це вирішить вашу проблему.


1

Це допомогло мені отримати більш плавне прокручування:

перевизначити onFailedToRecycleView (тримач ViewHolder) в адаптері

і зупинити будь-яку триваючу анімацію (якщо вона є). "animateview" .clearAnimation ();

пам’ятайте повернути справжнє;


1

Я вирішив це за допомогою цього рядка коду

recyclerView.setNestedScrollingEnabled(false);

1

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


0

i Вирішити цю проблему, використовуючи єдиний рядок у бібліотеці Пікассо

.fit ()

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();
                        }
                    });
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.