Przykład RecyclerView stworzyłem na podstawie przewodnika Tworzenie list i kart . Mój adapter ma implementację wzorca tylko do zawyżania układu.
Problem polega na słabej wydajności przewijania. To w RecycleView z tylko 8 elementami.
W niektórych testach sprawdziłem, że w Androidzie L ten problem nie występuje. Ale w wersji KitKat spadek wydajności jest ewidentny.
java
android
performance
android-recyclerview
falvojr
źródło
źródło
Odpowiedzi:
Niedawno spotkałem się z tym samym problemem, więc oto, co zrobiłem z najnowszą biblioteką wsparcia RecyclerView:
Zastąp złożony układ (widoki zagnieżdżone, RelativeLayout) nowym zoptymalizowanym ConstraintLayout. Aktywuj go w Android Studio: Idź do SDK Manager -> zakładka SDK Tools -> Support Repository -> sprawdź ConstraintLayout for Android & Solver for ConstraintLayout. Dodaj do zależności:
compile 'com.android.support.constraint:constraint-layout:1.0.2'
Jeśli to możliwe, nadaj wszystkim elementom RecyclerView tę samą wysokość . I dodaj:
recyclerView.setHasFixedSize(true);
Użyj domyślnych metod pamięci podręcznej rysunków RecyclerView i dostosuj je do swojego przypadku. Nie potrzebujesz do tego biblioteki innej firmy:
recyclerView.setItemViewCacheSize(20); recyclerView.setDrawingCacheEnabled(true); recyclerView.setDrawingCacheQuality(View.DRAWING_CACHE_QUALITY_HIGH);
Jeśli używasz wielu obrazów , upewnij się, że ich rozmiar i kompresja są optymalne . Skalowanie obrazów może również wpływać na wydajność. Istnieją dwie strony problemu - użyty obraz źródłowy i zdekodowana mapa bitowa. Poniższy przykład podpowiada, jak zdekodować obraz pobrany z sieci:
InputStream is = (InputStream) url.getContent(); BitmapFactory.Options options = new BitmapFactory.Options(); options.inPreferredConfig = Bitmap.Config.RGB_565; Bitmap image = BitmapFactory.decodeStream(is, null, options);
Najważniejsze jest określenie
inPreferredConfig
- określa, ile bajtów zostanie wykorzystanych na każdy piksel obrazu. Pamiętaj, że jest to preferowana opcja. Jeśli obraz źródłowy ma więcej kolorów, nadal będzie dekodowany przy użyciu innej konfiguracji.Upewnij się, że onBindViewHolder () jest tak tani, jak to tylko możliwe. Możesz ustawić OnClickListener raz
onCreateViewHolder()
i wywołać przez interfejs nasłuchiwanie poza adapterem, przekazując kliknięty element. W ten sposób nie tworzysz przez cały czas dodatkowych obiektów. Sprawdź także flagi i stany przed wprowadzeniem jakichkolwiek zmian w widoku tutaj.viewHolder.itemView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Item item = getItem(getAdapterPosition()); outsideClickListener.onItemClicked(item); } });
Gdy dane ulegną zmianie, spróbuj zaktualizować tylko te elementy . Na przykład zamiast unieważniać cały zestaw danych
notifyDataSetChanged()
przy dodawaniu / ładowaniu kolejnych elementów, wystarczy użyć:Z witryny sieci Web dla programistów Androida :
Ale jeśli musisz go użyć, utrzymuj swoje produkty z unikalnymi identyfikatorami :
adapter.setHasStableIds(true);
Nawet jeśli zrobisz wszystko dobrze, są szanse, że RecyclerView nadal nie działa tak płynnie, jak byś chciał.
źródło
recyclerView.setItemViewCacheSize(20);
mogą poprawić wydajność. AlerecyclerView.setDrawingCacheEnabled(true);
irecyclerView.setDrawingCacheQuality(View.DRAWING_CACHE_QUALITY_HIGH);
chociaż! Nie jestem pewien, czy to coś zmieni. Są toView
określone wywołania, które umożliwiają programowe pobranie pamięci podręcznej rysunku jako mapy bitowej i wykorzystanie jej później.RecyclerView
wydaje się, że nic z tym nie robi.Rozwiązałem ten problem dodając następującą flagę:
https://developer.android.com/reference/android/support/v7/widget/RecyclerView.Adapter.html#setHasStableIds(boolean)
źródło
Odkryłem co najmniej jeden wzorzec, który może zabić twoją wydajność. Pamiętaj, że
onBindViewHolder()
jest często nazywany . Więc wszystko, co robisz w tym kodzie, może spowodować zatrzymanie wydajności. Jeśli Twój RecyclerView dokonuje jakichkolwiek dostosowań, bardzo łatwo jest przypadkowo umieścić powolny kod w tej metodzie.Zmieniałem obrazy tła każdego RecyclerView w zależności od pozycji. Ale ładowanie obrazów wymaga trochę pracy, przez co mój RecyclerView jest powolny i szarpany.
Stworzenie pamięci podręcznej dla obrazów zdziałało cuda;
onBindViewHolder()
teraz tylko modyfikuje odniesienie do obrazu w pamięci podręcznej zamiast ładować go od zera. Teraz RecyclerView zapina się.Wiem, że nie każdy będzie miał dokładnie ten problem, więc nie kłopoczę się ładowaniem kodu. Ale proszę uważać każdą pracę wykonaną w Państwa
onBindViewHolder()
za potencjalną szyjkę butelki dla słabej wydajności RecyclerView.źródło
Oprócz szczegółowej odpowiedzi @ Galya, chcę stwierdzić, że chociaż może to być problem optymalizacji, prawdą jest również, że włączenie debugera może znacznie spowolnić.
Jeśli robisz wszystko, aby zoptymalizować swój program
RecyclerView
i nadal nie działa on płynnie, spróbuj przełączyć wariant kompilacji narelease
i sprawdź, jak działa w środowisku innym niż programistyczne (z wyłączonym debugerem).Zdarzyło mi się, że moja aplikacja działała wolno w
debug
wariancie kompilacji, ale jak tylko przełączyłem się narelease
wariant, działała płynnie. Nie oznacza to, że powinieneś programować zrelease
wariantem kompilacji, ale dobrze jest wiedzieć, że zawsze, gdy będziesz gotowy do wysłania aplikacji, będzie działać dobrze.źródło
Rozmawiałem o
RecyclerView
występie. Oto slajdy w języku angielskim i nagrane wideo w języku rosyjskim .Zawiera zestaw technik (niektóre z nich są już objęte odpowiedzią @ Darya ).
Oto krótkie podsumowanie:
Jeśli
Adapter
przedmioty mają stały rozmiar, ustaw:recyclerView.setHasFixedSize(true);
Jeśli jednostki danych mogą być reprezentowane przez długie (
hashCode()
na przykład), ustaw:adapter.hasStableIds(true);
i zaimplementuj:
// YourAdapter.java
@Override
public long getItemId(int position) {
return items.get(position).hashcode(); //id()
}
W tym przypadku
Item.id()
nie zadziała, ponieważ pozostanie niezmienione, nawet jeśliItem
zmieni się zawartość.PS Nie jest to konieczne, jeśli używasz DiffUtil!
Użyj poprawnie przeskalowanej mapy bitowej. Nie odkrywaj na nowo koła i nie korzystaj z bibliotek.
Więcej informacji jak wybrać tutaj .
Zawsze używaj najnowszej wersji
RecyclerView
. Na przykład nastąpiła ogromna poprawa wydajności25.1.0
- pobieranie wstępne.Więcej informacji tutaj .
Użyj DiffUtill.
DiffUtil jest koniecznością .
Oficjalna dokumentacja .
Uprość układ przedmiotu!
Niewielka biblioteka wzbogacająca TextViews - TextViewRichDrawable
Zobacz slajdy, aby uzyskać bardziej szczegółowe wyjaśnienia.
źródło
Nie jestem pewien, czy użycie
setHasStableId
flagi rozwiąże Twój problem. Na podstawie podanych informacji Twój problem z wydajnością może być związany z problemem z pamięcią. Wydajność aplikacji pod względem interfejsu użytkownika i pamięci jest dość powiązana.W zeszłym tygodniu odkryłem, że moja aplikacja przecieka pamięć. Odkryłem to, ponieważ po 20 minutach korzystania z mojej aplikacji zauważyłem, że interfejs użytkownika działa bardzo wolno. Zamykanie / otwieranie działania lub przewijanie RecyclerView z kilkoma elementami było naprawdę powolne. Po monitorowaniu niektórych moich użytkowników w produkcji za pomocą http://flowup.io/ znalazłem to:
Czas klatek był naprawdę wysoki, a liczba klatek na sekundę naprawdę niska. Jak widać, niektóre klatki potrzebowały około 2 sekund na renderowanie: S.
Próbując dowiedzieć się, co powodowało ten zły czas wyświetlania klatek / fps, odkryłem, że mam problem z pamięcią, jak widać tutaj:
Nawet gdy średnie zużycie pamięci było bliskie 15 MB, w tym samym czasie aplikacja gubiła klatki.
W ten sposób odkryłem problem z interfejsem użytkownika. Miałem wyciek pamięci w mojej aplikacji, powodując wiele zdarzeń odśmiecania pamięci, co powodowało złą wydajność interfejsu użytkownika, ponieważ maszyna wirtualna z systemem Android musiała zatrzymać moją aplikację, aby zebrać pamięć w każdej klatce.
Patrząc na kod, miałem wyciek w widoku niestandardowym, ponieważ nie wyrejestrowywałem nasłuchiwania z instancji Android Choreographer. Po wypuszczeniu poprawki wszystko wróciło do normy :)
Jeśli Twoja aplikacja gubi ramki z powodu problemu z pamięcią, zapoznaj się z dwoma typowymi błędami:
Sprawdź, czy Twoja aplikacja alokuje obiekty wewnątrz metody wywoływanej wiele razy na sekundę. Nawet jeśli ta alokacja może zostać wykonana w innym miejscu, w którym aplikacja staje się wolniejsza. Przykładem może być tworzenie nowych instancji obiektu wewnątrz niestandardowej metody widoku onDraw w onBindViewHolder w uchwycie widoku recyklera. Sprawdź, czy Twoja aplikacja rejestruje wystąpienie w Android SDK, ale go nie zwalnia. Zarejestrowanie słuchacza w zdarzeniu autobusowym może być również możliwym wyciekiem.
Zastrzeżenie: narzędzie, którego używam do monitorowania mojej aplikacji, jest w trakcie opracowywania. Mam dostęp do tego narzędzia, ponieważ jestem jednym z programistów :) Jeśli chcesz mieć dostęp do tego narzędzia, wkrótce udostępnimy wersję beta! Możesz dołączyć na naszej stronie internetowej: http://flowup.io/ .
Jeśli chcesz korzystać z różnych narzędzi, możesz użyć: traveview, dmtracedump, systrace lub monitora wydajności Andorid zintegrowanego z Android Studio. Pamiętaj jednak, że te narzędzia będą monitorować podłączone urządzenie, a nie pozostałe urządzenia użytkowników lub instalacje systemu operacyjnego Android.
źródło
Ważne jest również sprawdzenie układu nadrzędnego, w którym umieściłeś widok recyklingu. Miałem podobne problemy z przewijaniem, gdy testowałem recyclinglerView w nestedscrollview. Widok, który jest przewijany w innym widoku, którego przewijanie może pogorszyć wydajność podczas przewijania
źródło
W moim przypadku dowiedziałem się, że istotną przyczyną opóźnienia jest częste ładowanie ciągnione
#onBindViewHolder()
metodą wewnętrzną . Rozwiązałem to po prostu ładując obrazy jako Bitmap raz w ViewHolder i uzyskując do nich dostęp z wymienionej metody. To wszystko, co zrobiłem.źródło
W moim RecyclerView używam obrazów bitmapowych jako tła mojego elementu item_layout.
Wszystko, co powiedział @Galya, jest prawdą (i dziękuję mu za świetną odpowiedź). Ale dla mnie nie działały.
Oto, co rozwiązało mój problem:
BitmapFactory.Options options = new BitmapFactory.Options(); options.inSampleSize = 2; Bitmap bitmap = BitmapFactory.decodeStream(stream, null, options);
Aby uzyskać więcej informacji, przeczytaj tę odpowiedź .
źródło
W Mycase mam złożone dzieci z recyklingu. Więc wpłynęło to na czas ładowania aktywności (~ 5 sekund do renderowania aktywności)
Ładuję adapter za pomocą postDelayed () -> to da dobry wynik do renderowania aktywności. po aktywności renderującej ładowanie mojego recyklingu z płynnym.
Wypróbuj tę odpowiedź,
recyclerView.postDelayed(new Runnable() { @Override public void run() { recyclerView.setAdapter(mAdapter); } },100);
źródło
Widzę w komentarzach, że już implementujesz
ViewHolder
wzorzec, ale wrzucę tutaj przykładowy adapter wykorzystującyRecyclerView.ViewHolder
wzorzec, abyś mógł sprawdzić, czy integrujesz go w podobny sposób, znowu Twój konstruktor może się różnić w zależności od potrzeb, tutaj to przykład: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); } } }
Jeśli masz jakiekolwiek problemy z pracą,
RecyclerView.ViewHolder
upewnij się, że masz odpowiednie zależności, które możesz zawsze sprawdzić w Gradle PleaseMam nadzieję, że to rozwiązuje twój problem.
źródło
Pomogło mi to w płynniejszym przewijaniu:
Zastąp onFailedToRecycleView (uchwyt ViewHolder) w karcie
i zatrzymaj wszystkie trwające animacje (jeśli są). "animateview" .clearAnimation ();
pamiętaj, aby zwrócić prawdę;
źródło
Rozwiązałem to za pomocą tej linii kodu
recyclerView.setNestedScrollingEnabled(false);
źródło
Dodając do odpowiedzi @ Galya, w bind viewHolder użyłem metody Html.fromHtml (). najwyraźniej ma to wpływ na wydajność.
źródło
i Rozwiąż ten problem, używając jedynego wiersza w bibliotece Picassa
.dopasowanie()
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(); } });
źródło