Android RecyclerView Kaydırma Performansı


88

Listeler ve Kartlar Oluşturma kılavuzuna dayanarak RecyclerView örneği oluşturdum . Adaptörümde yalnızca düzeni şişirmek için bir desen uygulaması var.

Sorun, zayıf kaydırma performansıdır. Bu, yalnızca 8 öğeli bir RecycleView'da.

Bazı testlerde, Android L'de bu sorunun oluşmadığını doğruladım. Ancak KitKat versiyonunda performans düşüşü belirgindir.


1
Kaydırma performansı için ViewHolder tasarım modelini kullanmayı deneyin: developer.android.com/training/improving-layouts/…
Haresh Chhelana

@HareshChhelana cevabınız için teşekkürler! Ancak şu bağlantıya göre ViewHolder modelini zaten kullanıyorum: developer.android.com/training/material/lists-cards.html
falvojr

2
Adaptör kurulumunuz ve düzenleriniz için XML dosyası hakkında biraz kod paylaşabilir misiniz? Bu normal görünmüyor. Ayrıca, profilinizi çizip zamanın nerede harcandığını gördünüz mü?
yigit

2
Neredeyse aynı sorunlarla karşılaşıyorum. Lollipop öncesi hızlı ve Android L'de inanılmaz (gerçekten) yavaş olması dışında
Servus7

1
ayrıca içe aktardığınız kitaplığın sürümünü paylaşabilir misiniz?
Droidekas

Yanıtlar:


231

Yakın zamanda aynı sorunla karşılaştım, bu yüzden en son RecyclerView destek kitaplığında yaptığım şey bu:

  1. Karmaşık bir düzeni (yuvalanmış görünümler, RelativeLayout) yeni optimize edilmiş ConstraintLayout ile değiştirin. Android Studio'da etkinleştirin: SDK Manager -> SDK Tools sekmesine -> Support Repository -> ConstraintLayout for Android & Solver for ConstraintLayout'a gidin. Bağımlılıklara ekleyin:

    compile 'com.android.support.constraint:constraint-layout:1.0.2'
    
  2. Mümkünse, RecyclerView'ın tüm öğelerini aynı yükseklikte yapın . Ve Ekle:

    recyclerView.setHasFixedSize(true);
    
  3. Varsayılan RecyclerView çizim önbellek yöntemlerini kullanın ve durumunuza göre ince ayar yapın. Bunu yapmak için üçüncü taraf kitaplığına ihtiyacınız yoktur:

    recyclerView.setItemViewCacheSize(20);
    recyclerView.setDrawingCacheEnabled(true);
    recyclerView.setDrawingCacheQuality(View.DRAWING_CACHE_QUALITY_HIGH);
    
  4. Çok sayıda görüntü kullanıyorsanız , boyutlarının ve sıkıştırmasının en uygun olduğundan emin olun . Görüntüleri ölçeklemek de performansı etkileyebilir. Problemin iki tarafı vardır - kullanılan kaynak görüntü ve kodu çözülmüş Bitmap. Aşağıdaki örnek, web'den indirilen bir görüntünün kodunu nasıl çözeceğiniz konusunda size bir ipucu verir:

    InputStream is = (InputStream) url.getContent();
    BitmapFactory.Options options = new BitmapFactory.Options();
    options.inPreferredConfig = Bitmap.Config.RGB_565;
    Bitmap image = BitmapFactory.decodeStream(is, null, options);
    

En önemli kısım, inPreferredConfiggörüntünün her pikseli için kaç bayt kullanılacağını belirlemektir. Unutmayın ki bu bir tercih edilen bir seçenek . Kaynak görüntünün daha fazla rengi varsa, yine de farklı bir yapılandırmayla kodu çözülecektir.

  1. OnBindViewHolder () ' ın olabildiğince ucuz olduğundan emin olun . OnClickListener'ı bir defada ayarlayabilirsiniz.onCreateViewHolder() ve bir arabirim aracılığıyla Bağdaştırıcının dışındaki bir dinleyiciyi tıklanan öğeyi ileterek çağırabilirsiniz. Bu şekilde her zaman fazladan nesneler yaratmazsınız. Ayrıca, burada görünümde herhangi bir değişiklik yapmadan önce bayrakları ve durumları kontrol edin.

    viewHolder.itemView.setOnClickListener(new View.OnClickListener() {
          @Override
          public void onClick(View view) {
              Item item = getItem(getAdapterPosition());
              outsideClickListener.onItemClicked(item);
          }
    });
    
  2. Veriler değiştiğinde, yalnızca etkilenen öğeleri güncellemeye çalışın . Örneğin notifyDataSetChanged(), daha fazla öğe eklerken / yüklerken tüm veri kümesini geçersiz kılmak yerine şunu kullanın:

    adapter.notifyItemRangeInserted(rangeStart, rangeEnd);
    adapter.notifyItemRemoved(position);
    adapter.notifyItemChanged(position);
    adapter.notifyItemInserted(position);
    
  3. Gönderen Android Geliştirici Web Sitesi :

Son çare olarak notifyDataSetChanged () 'e güvenin.

Ancak kullanmanız gerekiyorsa, öğelerinizi benzersiz kimliklerle koruyun :

    adapter.setHasStableIds(true);

RecyclerView, bu yöntem kullanıldığında kararlı kimliklere sahip olduklarını bildiren bağdaştırıcılar için görünür yapısal değişiklik olaylarını sentezlemeye çalışacaktır. Bu, animasyon ve görsel nesne sürekliliği açısından yardımcı olabilir, ancak tek tek öğe görünümlerinin yine de geri tepmesi ve yeniden dağıtılması gerekecektir.

Her şeyi doğru yapsanız bile, RecyclerView hala istediğiniz kadar sorunsuz çalışmıyordur.


20
Adapter.setHasStableIds için bir oy (true); geri dönüşüm görünümünün hızlı olmasına gerçekten yardımcı olan yöntem.
Atula

1
Bölüm 7 tamamen yanlış! setHasStableIds (true), adapter.notifyDataSetChanged () kullanmanız dışında hiçbir şey yapmaz. Bağlantı: developer.android.com/reference/android/support/v7/widget/…
localhost

1
recyclerView.setItemViewCacheSize(20);Performansı neden etkileyebileceğini anlayabiliyorum. FakatrecyclerView.setDrawingCacheEnabled(true); ve recyclerView.setDrawingCacheQuality(View.DRAWING_CACHE_QUALITY_HIGH);olsa! Bunların bir şeyi değiştireceğinden emin değilim. Bunlar View, çizim önbelleğini bir bit eşlem olarak programlı olarak almanıza ve daha sonra kendi yararınız için kullanmanıza olanak tanıyan özel çağrılardır. RecyclerViewbu konuda hiçbir şey yapmıyor gibi görünüyor.
Abdelhakim AKODADI

2
@AbdelhakimAkodadi, kaydırma önbellekle pürüzsüz hale geliyor. Test ettim. Bunun aksini nasıl söylersin, çok açık. Elbette, birisi deli gibi kaydırırsa hiçbir şey yardımcı olmaz. Sadece setDrawingCacheQuality gibi kullanmadığım diğer seçenekleri gösteriyorum çünkü benim durumumda görüntü kalitesi önemli. DRAWING_CACHE_QUALI‌ TY_HIGH vaaz vermiyorum, ancak daha derine inmek ve seçeneği değiştirmek isteyenlere öneriyorum.
Galya

2
setDrawingCacheEnabled () ve setDrawingCacheQuality () kullanımdan kaldırılmıştır. Bunun yerine donanım hızlandırmayı kullanın. developer.android.com/reference/android/view/…
Shayan_Aryan

14

2
Bu benim için küçük bir performans artışı sağlıyor. Ancak RecyclerView hala ölü köpek yavaştır - eşdeğer bir özel ListView'den çok daha yavaştır.
SMBiggs

Ahh, ama kodumun neden bu kadar yavaş olduğunu keşfettim - setHasStableIds () ile ilgisi yok. Daha fazla bilgi içeren bir cevap göndereceğim.
SMBiggs

13

Performansınızı öldürebilecek en az bir model keşfettim. Unutma onBindViewHolder()buna denir sık sık . Yani bu kodda yaptığınız her şey, performansınızı durma noktasına getirme potansiyeline sahiptir. RecyclerView'unuz herhangi bir özelleştirme yaparsa, bu yönteme yanlışlıkla bazı yavaş kodlar yerleştirmek çok kolaydır.

Her RecyclerView'ın arka plan resimlerini konuma bağlı olarak değiştiriyordum. Ancak görüntüleri yüklemek biraz iş gerektirir ve RecyclerView'umun yavaş ve sarsıntılı olmasına neden olur.

Görüntüler için bir önbellek oluşturmak harikalar yarattı; onBindViewHolder()artık sıfırdan yüklemek yerine önbelleğe alınmış bir görüntüye yapılan referansı değiştiriyor. Şimdi RecyclerView birlikte fermuarlar.

Herkesin tam olarak bu sorunu yaşamayacağını biliyorum, bu yüzden kodu yükleme zahmetine girmiyorum. Ancak lütfen sizde yapılan herhangi bir işi, onBindViewHolder()düşük RecyclerView performansı için potansiyel bir dar boğaz olarak düşünün .


Bendede aynı sorun var. Şu anda resim yükleme ve önbelleğe alma için Fresco kullanıyorum. RecyclerView'da görüntüleri yüklemek ve önbelleğe almak için başka, daha iyi bir çözümünüz var mı? Teşekkürler.
Androidicus

Fresco'ya aşina değilim (okumak ... vaatleri güzel). Belki önbelleklerini RecyclerViews ile en iyi şekilde nasıl kullanacaklarına dair bazı içgörülere sahiplerdir. Ve görüyorum ki bu sorunu olan tek kişi siz değilsiniz: github.com/facebook/fresco/issues/414 .
SMBiggs

10

@ Galya'nın ayrıntılı cevabına ek olarak, bir optimizasyon sorunu olsa da, hata ayıklayıcının etkinleştirilmesinin işleri büyük ölçüde yavaşlatabileceğini de belirtmek isterim.

Kendinizi optimize etmek için her şeyi yapıyorsanız RecyclerViewve hala sorunsuz çalışmıyorsa, yapı varyantınızı olarak değiştirmeyi deneyin releaseve geliştirme dışı bir ortamda (hata ayıklayıcı devre dışı bırakılmış olarak) nasıl çalıştığını kontrol edin.

Uygulamamın debugyapı varyantında yavaş performans gösterdiğini fark ettim , ancak releasevaryanta geçer geçmez sorunsuz çalıştı. Bu, releasederleme varyantını geliştirmeniz gerektiği anlamına gelmez , ancak uygulamanızı göndermeye hazır olduğunuzda, gayet iyi çalışacağını bilmek iyidir.


Bu yorum benim için gerçekten yardımcı oldu! Geri dönüştürücümün performansını iyileştirmek için her şeyi denedim ama hiçbir şey gerçekten yardımcı olmadı, ancak sürüm yapısına geçtikten sonra her şeyin yolunda olduğunu fark ettim.
Tal Barda

1
Hatta bağlı ayıklayıcıya olmadan aynı sorunla karşı karşıya ama en kısa sürüm oluşturma için harekete geçip sorunu artık görülebilir oldu
Farmaan Elahi

8

RecyclerViewOnun performansı hakkında konuştum . İşte İngilizce slaytlar ve Rusça olarak kaydedilmiş videolar .

Bir dizi teknik içerir (bunlardan bazıları @ Darya'nın cevabı kapsamındadır ).

İşte kısa bir özet:

  • Eğer Adapterürün belirlenmiş büyüklükleri sonra ayarlayın:
    recyclerView.setHasFixedSize(true);

  • Veri varlıkları uzun ile temsil edilebiliyorsa ( hashCode()örneğin) o zaman set:
    adapter.hasStableIds(true);
    and implement:
    // YourAdapter.java
    @Override
    public long getItemId(int position) {
    return items.get(position).hashcode(); //id()
    }
    Bu durumda Item.id()çalışmaz, çünkü Itemiçeriği değişse bile aynı kalır .
    PS DiffUtil kullanıyorsanız bu gerekli değildir!

  • Doğru ölçeklenmiş bitmap kullanın. Tekerleği yeniden icat etmeyin ve kitaplıkları kullanmayın.
    Nasıl seçileceği hakkında daha fazla bilgi burada .

  • Daima uygulamasının en son sürümünü kullanın RecyclerView. Örneğin, 25.1.0ön yüklemede büyük performans iyileştirmeleri vardı .
    Daha fazla bilgi burada .

  • DiffUtill kullanın.
    DiffUtil bir zorunluluktur .
    Resmi belgeler .

  • Öğenizin düzenini basitleştirin!
    Metin Görünümlerini zenginleştirmek için küçük kitaplık - TextViewRichDrawable

Daha ayrıntılı açıklama için slaytlara bakın .


7

setHasStableIdBayrak kullanımının sorununuzu çözüp çözmeyeceğinden pek emin değilim . Sağladığınız bilgilere dayanarak, performans sorununuz bir bellek sorunuyla ilgili olabilir. Kullanıcı arayüzü ve bellek açısından uygulamanızın performansı oldukça ilişkilidir.

Geçen hafta uygulamamın bellek sızdırdığını keşfettim. Bunu keşfettim çünkü uygulamamı 20 dakika kullandıktan sonra kullanıcı arayüzünün gerçekten yavaş çalıştığını fark ettim. Bir aktiviteyi kapatmak / açmak veya RecyclerView'ı bir grup öğe ile kaydırmak gerçekten yavaştı. Bazı kullanıcılarımı http://flowup.io/ kullanarak üretimde izledikten sonra şunu buldum:

görüntü açıklamasını buraya girin

Çerçeve süresi gerçekten çok yüksekti ve saniyedeki kare sayısı gerçekten çok düşüktü. Bazı karelerin işlenmesi için yaklaşık 2 saniye gerektiğini görebilirsiniz: S.

Bu kötü çerçeve süresine / fps'ye neyin neden olduğunu anlamaya çalışıyorum, burada görebileceğiniz gibi bir bellek sorunum olduğunu keşfettim:

görüntü açıklamasını buraya girin

Ortalama bellek tüketimi aynı anda 15MB'ye yakın olduğunda bile uygulama kare düşürüyordu.

UI sorununu böyle keşfettim. Uygulamamda çok sayıda çöp toplayıcı olayına neden olan bir bellek sızıntısı vardı ve bu, kötü UI performansına neden oluyordu çünkü Android sanal makinesi, uygulamamı her karede bellek toplamak için durdurmak zorunda kaldı.

Koda baktığımda, Android Choreographer örneğinden bir dinleyicinin kaydını iptal etmediğim için özel bir görünümde sızıntı yaşadım. Düzeltmeyi yayınladıktan sonra her şey normale döndü :)

Uygulamanız bir bellek sorunu nedeniyle çerçeve atıyorsa, iki yaygın hatayı incelemelisiniz:

Uygulamanızın saniyede birden çok kez çağrılan bir yöntem içinde nesneleri ayırıp ayırmadığını inceleyin. Hatta bu tahsis, uygulamanızın yavaşladığı farklı bir yerde yapılabilir. Geri dönüşümcü görünüm tutucunuzdaki onBindViewHolder üzerinde bir onDraw özel görünüm yöntemi içinde bir nesnenin yeni örneklerini oluşturmak bir örnek olabilir. Uygulamanızın Android SDK'ya bir örnek kaydedip kaydetmediğini ancak yayınlamadığını inceleyin. Bir dinleyicinin bir veri yolu olayına kaydedilmesi de olası sızıntı olabilir.

Sorumluluk Reddi: Uygulamamı izlemek için kullandığım araç geliştirme aşamasındadır. Bu araca erişimim var çünkü geliştiricilerden biriyim :) Bu araca erişmek istiyorsanız yakında bir beta sürümünü yayınlayacağız! Web sitemize katılabilirsiniz: http://flowup.io/ .

Farklı araçlar kullanmak istiyorsanız kullanabilirsiniz: traveview, dmtracedump, systrace veya Android Studio'ya entegre Andorid performans monitörü. Ancak bu araçların, kullanıcı cihazlarınızın veya Android işletim sistemi kurulumlarınızın geri kalanını değil, bağlı cihazınızı izleyeceğini unutmayın.


3

Recyclerview'e koyduğunuz ana sayfa düzenini kontrol etmek de önemlidir. RecyclerView'i yuvalanmış bir kaydırma görünümünde test ederken benzer kaydırma sorunları yaşadım. Kaydırma sırasında performansta düşüşe neden olabilecek başka bir görünümde kayan bir görünüm


2

Benim durumumda, gecikmenin önemli nedeninin sık sık çekilebilir iç yükleme #onBindViewHolder()yöntemi olduğunu öğrendim . ViewHolder'ın içine görüntüleri bir kez Bitmap olarak yükleyerek ve belirtilen yöntemden erişerek çözdüm . Tüm yaptığım bu.


2

RecyclerView'da item_layout'umun arka planı için Bitmap Görüntüleri kullanıyorum.
@Galya'nın söylediği her şey doğru (ve harika cevabı için kendisine teşekkür ediyorum). Ama benim için çalışmadılar.

Sorunumu çözen şey buydu:

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

Daha fazla bilgi için lütfen bu Cevabı okuyun .


2

Benim durumumda karmaşık geri dönüşümlü çocuklara sahibim. Dolayısıyla, etkinlik yükleme süresini etkiledi (etkinlik oluşturma için ~ 5 saniye)

Bağdaştırıcıya postDelayed () -> yükledim -> bu, etkinlik oluşturma için iyi bir sonuç verecektir. etkinlikten sonra geri dönüşüm cihazımı sorunsuz bir şekilde yükledikten sonra.

Bu cevabı dene,

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

1

Yorumlarda ViewHolderkalıbı zaten uyguladığınızı görüyorum, ancak burada RecyclerView.ViewHolderkalıbı kullanan bir örnek bağdaştırıcı göndereceğim, böylece onu benzer bir şekilde entegre ettiğinizi doğrulayabilirsiniz, yine kurucunuz ihtiyaçlarınıza bağlı olarak değişebilir, burada bir örnektir:

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

İle çalışırken herhangi bir sorun yaşıyorsanız RecyclerView.ViewHolderÇalışırken yaşarsanız, her zaman Gradle'da doğrulayabileceğiniz uygun bağımlılıklara sahip olduğunuzdan emin olun Lütfen

Umarım sorununuzu çözer.


1

Bu, daha düzgün kaydırma yapmama yardımcı oldu:

adaptörde onFailedToRecycleView (ViewHolder tutucusu) öğesini geçersiz kılın

ve devam eden animasyonları (varsa) durdurun. "animateview" .clearAnimation ();

doğruya dönmeyi unutmayın;


1

Bu kod satırıyla çözdüm

recyclerView.setNestedScrollingEnabled(false);

1

@ Galya'nın cevabına ek olarak bind viewHolder'da Html.fromHtml () metodunu kullanıyordum. görünüşe göre bunun performans etkisi var.


0

i Picasso kitaplığındaki tek satırı kullanarak bu sorunu çözün

.Uygun()

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();
                        }
                    });
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.