Optimizing RecyclerView : Pre-inflated ViewPool

by | Aug 23, 2022 | Tech

In any real-world android application, Recylerviews generally do not display static data, data is fetched from the server and then displayed on the UI. When we get a response from the server, only then data is given to the Recyclerview’s adapter which then first creates the items if they are not already present in the RecyclerviewPool (initially pool is empty) and then draws them on the screen. Since all this is done just before they need to be displayed, if the layout is complex enough, the UI thread gets really busy which then leads to frozen frames.

Before understanding how this optimization works and how to implement it in your adapters. Let’s see what improvements we are able to achieve using it.

The above screenshot is from Firebase’s Performance Monitoring, showing the decrease in load time after implementing this in our base adapters.

The above graph is from our in-house monitoring framework showing load time in two different versions of the live application. 0.07 and 0.19 are load times in seconds.
From these metrics, we can see that this optimization not only works but can significantly improve performance.

Before digging deep into this optimization let’s briefly discuss how and when views are created.

Unlike onBindViewHolder which is called multiple times, onCreateViewHolder is only called when RecyclerView needs a new ViewHolder of the given type to represent an item. Because the creation of views is very expensive for the CPU, it is done only when it’s needed. That’s why the concept of recycling was introduced to minimize the creation of views by updating the already existing views using onBindViewHolder method.

So, We can significantly improve the performance by making these expensive calls when the UI thread is not doing much.

What are we trying to solve here?

Problem #1:
Recylcerview creates items just before they are to be used and displayed on the UI. This can result in frozen frames if we have complex UI.

Problem #2:
If we have a nested Recyclerview then the child views can only be created after their parents are created because at the time of inflation we need to pass the parent.

Solution:

The whole idea behind this optimization was that we have plenty of free time on the UI thread between hitting the API and getting the response. We can utilize that time to inflate some views and make a pool of them that can be used later. And thus utilizing the free UI thread and reducing the overall first draw time for the Recyclerview, leads to a reduction of frozen frames.

Alright, I know all this sounds like magic but it’s time to reveal the trick.

For this, we will make a separate class PrefetchViewPool and make an object of it inside our custom adapter.

https://tech.winzogames.com/media/032352c11817bef7101edd1b9d69c000

PrefetchViewPool

I know the name sounds cool 🙂 but what it does is even cooler 🤩.

Firstly, We need our custom adapter to tell PrefetchViewPool about the count and the layout ID of the viewType we want to pre-inflate.

For this, we will maintain three HashMaps in PrefetchViewPool

  1. prefetchViewTypeList —
    key: parent view’s layout ID
    value: prefetch count ( number of views we want it to pre-inflate.)
  2. prefetchItemBindingMap —
    key: view’s layout ID
    value: ViewBindings
  3. prefetchChildViewTypeList —
    key: parent view’s layout ID
    value: child view’s layout ID and it’s prefetch count (number of child views we want to pre-inflate)

The prefetch count will depend upon the number of views visible on the UI initially + scrap views. You can also play with this number a bit as per your requirement. Caution: setting this value too high might impact the performance negatively.

https://tech.winzogames.com/media/1d76c8a7eaefc848944eb4fedf145c5d

Since we need the parent view to which the items will get attached, we will start prefetching on the call of onAttachedToRecyclerView() method.

https://tech.winzogames.com/media/bf66c39dcbb55d4c748fb5fef72b73ce

Now let us see what this prefetchItemBinding() method does.

https://tech.winzogames.com/media/6c8e61a3d1151f806820b1dd64183f04

As we can see, this method first checks if the prefetchViewTypeListis empty or not. If not, it calls the initAsyncItemBinding() method for each iteration.

Now let’s see what the initAsyncItemBinding() method does:

https://tech.winzogames.com/media/eb9bbd58347070c513bac196289f0273

In this method, we are inflating items asynchronously. It iterates as per the given count and stores the inflated ViewBindings in prefetchItemBindingMap with their respective layout ID as the key.

Then finally we will call prefetchChildItemBinding() method to inflate its children’s views if there are any. We need to call this method only once and that can be done for any parent. (here we are calling it for index 0 as it doesn’t matter to which parent the child gets attached)

Let’s see what this method does:

https://tech.winzogames.com/media/f5842d4540382fb1b6825702009963fd

Before inflating child views, we have to pass some validations first. If any of the below conditions satisfies no child’s views will be inflated.

  1. If the adapter is not attached, there is no need to inflate any view.
  2. There is no parent view binding present for these children, no inflation will be done because we need parents to inflate the child.
  3. If there are no views to pre-inflate. (prefetchChildViewTypeList is empty)
  4. If there are no child views present for this parent.
  5. If this viewType of children is already inflated by some other parent view.

If none of the conditions satisfies, only then child views will be inflated. They will be inflated similarly by calling the initAsyncItemBinding method. One important and interesting thing to notice here is that if the child views have their own child views, this will get handled itself by the initAsyncItemBinding method.

You know what, all the heavy lifting is done🥳🎉 and now we just have to start using it with our custom adapter.

We will be using these two methods mentioned below to set the values in prefetchViewTypeList and prefetchChildViewTypeList .

https://tech.winzogames.com/media/0024006cacd1c82dc142cfdfb8d368c1

In our custom adapter’s init block, we will be passing the values to these methods.

https://tech.winzogames.com/media/9a3781ebc4e18208250454b87f715370

We will get pre-inflated views using getPrefetchedItemBinding() method. We have to pass parent and layout ID to this in arguments. First, it will check if there is any prefetched view in the prefetchItemBindingMap if not then it will simply inflate the view as per normal flow and return the ViewDataBiding.

https://tech.winzogames.com/media/3ba7db8d59222bfb750048d365ee8a82

Now the question is where will we call this method from?

This method will be simply called inside onCreateViewHolder() instead of inflating the item.

https://tech.winzogames.com/media/817a1b9c841d987ffb3608eb4c8bba1e

And thus do not have to inflate new items until the prefetchItemBindingMap gets empty and after that new items will get inflated as they would have if there were no PrefetchViewPool implemented.

And This is it!! We are done and it’s time for a victory dance.

I know all this still sounds magical🪄🤩

If you still don’t believe me then I’ve two things for you.

  1. Please visit a therapist because you have big trust issues😂
  2. I can lie to you but Android Profiler won’t. So, below are the results from CPU profiling.

Without PrefetchViewPool

With PrefetchViewPool

The traces in the above profiling starts when we set the data to adapter and call notifyDataSetChanged() and stops at onLayoutCompleted() .

Key points to notice in the above results:

  1. Load time reduced from 214.19ms to 118.35ms🔥
  2. If you carefully observe, you’ll notice yellow boxes inside RV OnLayout trace has practically been reduced to zero as no onCreateViewHolder() are being made after implementing PrefetchViewPool.

Link to Git repository —

https://github.com/shivamdhammi/prefetchViewPool

Special mentions: 

Anshul Agarwal and Parth Gupta

You May Also Like…

Building Scalable A/B Testing Microservice in Go

Building Scalable A/B Testing Microservice in Go

Introduction If you are here, you probably are already familiar with A/B testing. Instead of defining what it is, let’s skip to the good part. Tech Design We wanted to build a micro-service for A/B experiments which can run at scale and can be used by other backend...

Blue/Green deployment for WinZO API Gateway

Blue/Green deployment for WinZO API Gateway

Introduction: There’s a lot that goes behind the scenes in creating India’s largest social gaming platform serving millions of users and handling outrageous amounts of requests every minute. In this ever evolving industry, there’s hundreds of new exciting games,...