Upgrade to Pro — share decks privately, control downloads, hide ads and more …

The World of RecyclerView. ENG (Droidcon Krakow...

The World of RecyclerView. ENG (Droidcon Krakow, 2017)

Version with Animations - https://docs.google.com/presentation/d/1P_JAw4SmVu2vYOxSWtVv__xr_e544CF0JbQHZ_tTcEg

RecyclerView has been a common part of every Android application's development process since Google released the support library with optimized ListView replacement. Besides improvements made by the Google team, there are some techniques which can be applied to RecyclerView to make it more efficient. Some of these approaches were introduced by the creators, some were discovered by enthusiastic developers. I have gathered all these best practices together, added my own ones and some experiments on top of them.

Oleksandr Tolstykh

December 01, 2017
Tweet

More Decks by Oleksandr Tolstykh

Other Decks in Programming

Transcript

  1. RecyclerView vs ListView @a_tolstykh - Animations - Advanced API -

    Performance - Other improvements @a_tolstykh
  2. How to measure UI performance? • FPS https://github.com/friendlyrobotnyc/TinyDancer • Profile

    GPU rendering https://developer.android.com/studio/profile/dev-options-rendering.html • Aggregate frame stats https://developer.android.com/training/testing/performance.html • Many others... @a_tolstykh
  3. How to measure UI performance? • FPS https://github.com/friendlyrobotnyc/TinyDancer • Profile

    GPU rendering https://developer.android.com/studio/profile/dev-options-rendering.html • Aggregate frame stats https://developer.android.com/training/testing/performance.html • Many others... @a_tolstykh
  4. Sample App - Travel With Us City guides Base city

    info Data is loaded from server RecyclerView! @a_tolstykh
  5. Optimize Cells hierarchies Avoid deep hierarchy Save level with <merge>

    when <include> Layout matters! android.support.constraint.ConstraintLayout com.google.android.flexbox.FlexboxLayout https://developer.android.com/topic/performance/rendering/optimizing-view-hierarchies.html @a_tolstykh
  6. Minimize onBindViewHolder() Make onBindViewHolder() as cheap as possible Avoid item

    instantiations (memory allocations) Do as much as you can in onCreateViewHolder() @a_tolstykh
  7. Correct images scale Make sure their size and compression are

    optimal. Scaling images may also affect the performance. Do not reinvent the wheel. Picasso, ImageLoader, Fresco, Glide. Picasso v/s Imageloader v/s Fresco vs Glide [closed] http://stackoverflow.com/q/29363321/2308720 @a_tolstykh
  8. Remove an item with swipe: val touchCallback = object :

    SimpleCallback(0, LEFT or RIGHT) { // ... override fun onSwiped(viewHolder: ViewHolder, swipeDir: Int) { adapter.remove(viewHolder.getAdapterPosition()) adapter.notifyItemRemoved(viewHolder.getAdapterPosition()) } } // attaching the touch helper to recycler view ItemTouchHelper(touchCallback).attachToRecyclerView(recyclerView); @a_tolstykh
  9. Items cache recyclerView.setItemViewCacheSize(size: Int) Documentation: “Set the number of offscreen

    views to retain before adding them to the potentially shared recycled view pool.” static final int DEFAULT_CACHE_SIZE = 2; // supportVersion = 25.3.0 @a_tolstykh
  10. Prefetch Use latest version of support library to use native

    prefetch optimizations supportVersion >= 25.1.0 // (enabled by default) RecyclerView Prefetch by Chet Haase https://medium.com/google-developers/c2f269075710 *Lollipop and newer @a_tolstykh
  11. Advanced prefetch class PreCachingLayoutManager(context: Context) : LinearLayoutManager(context) { private var

    customExtraLayoutSpace: Int = -1 set(value) { field = value } override fun getExtraLayoutSpace(state: RecyclerView.State): Int { return if (customExtraLayoutSpace >= 0) customExtraLayoutSpace else super.getExtraLayoutSpace(state) } } @a_tolstykh
  12. BUT... Expensive if done while the user may change scrolling

    direction. Laying out invisible elements generally comes with significant performance cost. Useless without increasing cache size (setItemViewCacheSize). BUT it improves USER EXPERIENCE! Need to find a balance. Maybe 1 extra screen (=default) is The Balance? @a_tolstykh
  13. Even more advanced prefetch... Prefetch images! (on wi-fi only) Picasso.with(context).load(url).fetch()

    Glide.with(context).load(url).downloadOnly(width, height) @a_tolstykh
  14. class CitiesAdapter : RecyclerView.Adapter<CityViewHolder>() { fun updateData(cities: List<City>) { if

    (onWifi()) prefetch(cities) // TODO update adapter data } private fun prefetch(cities: List<City>) { for (city in cities) { val prefetcher = city.getPrefetcher() if (prefetcher != null) { prefetcher.prefetch(mContext) } } } } @a_tolstykh
  15. class ImagePrefetcher(private val images: List<Image>) : PreFetcher { fun prefetch(context:

    Context) { for (image in images) { Picasso.with(context).load(image.url()).fetch() } } } @a_tolstykh
  16. class TweetPrefetcher(private val tweetIds: List<Int>) : PreFetcher { fun prefetch(context:

    Context) { TwitterHelper.loadTweets(tweetIds, object : Callback<List<Tweet>>() { fun success(result: Result<List<Tweet>>) { for (tweet in result.data) { TwitterHelper.preFetchTweetImage(tweet) } } }) } } @a_tolstykh
  17. Displayed data pre-calculations Displayed items are POJOs (Kotlin data classes).

    Pre-format date, time, other strings during data parsing. Pre-calculate immutable values during data parsing. Profit: + Minor performance optimisations. + Formatting is done in BG thread. + Formatting is done in single place. @a_tolstykh
  18. Displayed data pre-calculations (formatting) @WorkerThread private fun createRating(value: Float, count:

    Int): Rating { val ratingFormatted = String.format(Locale.US, "%.1f", value) val countFormatted = formatWithMetricPrefix(count) return Rating.create(value, ratingFormatted, count, countFormatted) } @UiThread fun setRating(rating: Rating) { ratingText.setText(rating.ratingFormatted()) reviewsCount.setText(rating.numberOfReviewsFormatted()) } @a_tolstykh
  19. android.support.v7.util.DiffUtil Documentation: “DiffUtil is a utility class that can calculate

    the difference between two lists and output a list of update operations that converts the first list into the second one. It can be used to calculate updates for a RecyclerView Adapter.” @a_tolstykh
  20. DiffUtil.Callback class DiffCallback(val newList: List<City>, val oldList: List<City>) : DiffUtil.Callback()

    { override fun getOldListSize(): Int = oldList.size override fun getNewListSize(): Int = newList.size override fun areItemsTheSame(oldItemPos: Int, newItemPos: Int): Boolean = oldList.get(oldItemPos).itemId == newList.get(newItemPos).itemId override fun areContentsTheSame(oldItemPos: Int, newItemPos: Int): Boolean = oldList.get(oldItemPos) == newList.get(newItemPos) @a_tolstykh
  21. Without DiffUtil With DiffUtil Lenovo Vibe X Android OS, v4.2

    1080 x 1920 pixels Quad-core 1.5 GHz @a_tolstykh
  22. DiffUtil animated values change override fun getChangePayload(oldItemPosition: Int, newItemPosition: Int):

    Any? { val diff = Bundle() val newRating = newList.get(newItemPosition).rating() val oldRating = oldList.get(oldItemPosition).rating() if (newRating.numberOfReviews() !== oldRating.numberOfReviews()) { diff.putString(KEY_NUMBER_OF_REVIEWS, newRating.numberOfReviewsFormatted()) } if (newRating.rating().compareTo(oldRating.rating()) != 0) { diff.putString(KEY_RATING_FORMATTED, newRating.ratingFormatted()) } return if (diff.size() == 0) null else diff } @a_tolstykh
  23. override fun onBindViewHolder(holder: CityViewHolder, index: Int, p: List<Any>) { if

    (p.isEmpty()) { onBindViewHolder(holder, index) return } val payload = p[0] as Bundle for (key in payload.keySet()) { if (key == CitiesDiffCallback.KEY_NUMBER_OF_REVIEWS) { holder.animateReviews(payload.getString(key)) } else if (key == CitiesDiffCallback.KEY_RATING_FORMATTED) { holder.animateRating(payload.getString(key)) } } } @a_tolstykh
  24. DiffUtil - average runtimes • 100 items and 10 modifications:

    avg: 0.39 ms, median: 0.35 ms • 100 items and 100 modifications: 3.82 ms, median: 3.75 ms • 100 items and 100 modifications without moves: 2.09 ms, median: 2.06 ms • 1000 items and 50 modifications: avg: 4.67 ms, median: 4.59 ms • 1000 items and 50 modifications without moves: avg: 3.59 ms, median: 3.50 ms • 1000 items and 200 modifications: 27.07 ms, median: 26.92 ms • 1000 items and 200 modifications without moves: 13.54 ms, median: 13.36 ms tests are run on Nexus 5X with M @a_tolstykh
  25. Android TextView with rich support of compound drawables. This is

    a tiny library which empowers TextView's compound drawables with: • size specifying • vector support • tinting https://github.com/a-tolstykh/textview-rich-drawable TextViewRichDrawable @a_tolstykh
  26. • Optimized layout TextViewRichDrawable • More readable code • Better

    User Experience @a_tolstykh https://github.com/a-tolstykh/textview-rich-drawable
  27. <TextView android:id="@+id/reviews_count" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <ImageView android:layout_width="@dimen/icon_small_size" android:layout_height="@dimen/icon_small_size" android:layout_marginEnd="@dimen/space_medium" android:src="@drawable/ic_person"

    /> <TextView android:id="@+id/rating_value" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <ImageView android:layout_width="@dimen/icon_small_size" android:layout_height="@dimen/icon_small_size" android:src="@drawable/ic_star" /> <com.tolstykh.textviewrichdrawable.TextViewRichDrawable android:id="@+id/reviews_count" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginEnd="@dimen/space_medium" android:drawableRight="@drawable/ic_person" app:compoundDrawableHeight="@dimen/icon_small_size" app:compoundDrawableWidth="@dimen/icon_small_size" /> <com.tolstykh.textviewrichdrawable.TextViewRichDrawable android:id="@+id/rating_value" android:layout_width="wrap_content" android:layout_height="wrap_content" android:drawableRight="@drawable/ic_star" app:compoundDrawableHeight="@dimen/icon_small_size" app:compoundDrawableWidth="@dimen/icon_small_size" /> Before After @a_tolstykh https://github.com/a-tolstykh/textview-rich-drawable