How to Cook the 60 FPS RecyclerView?
By Dmitry Rabetckiy, Android Developer at Rosberry
Almost in every Android-based application there are item lists in one form or another. To display those lists on the screen developers use RecyclerView as the most fluent, friendly and resource non-intensive component. But, as it always happens, one and the same component in different use environments can behave absolutely differently. So, I propose that we outline the conditions we usually have to deal with and divide them into two groups:
Those we can influence:
- Nesting level as relating to other Views.
- Number of Views on one screen.
- “Light” ViewHolder.
- Number of unique ViewHolders.
- Animation inside ViewHolder as well as outside RecyclerView.
- RecyclerView Settings.
Those we can not influence:
- Software (Android implementation).
- Utilised capacity of the device.
As follows from Group 1 above there is quite a lot of leverage we can use to reach the goal. But let’s expand on each of the items in more detail.
It’s worth starting with that to render the whole 60 FPS screen we have only 16 ms per frame. In fact, we have to use more than 90% of time to render the app a user sees, considering the recent trends of edge-to-edge screens.
Nesting level as relating to other Views
The lower the layout hierarchy of the View, the more time-consuming rendering and calculation of hierarchy boundaries will be. You should consider this fact laying out a screen.
Solution: RecyclerView should be in the very root of the hierarchy of your screen.
Number of Views on one screen
Due to the specific character of View implementation on Android — it’s a pretty ‘heavy’ framework element where there are calculation of View in the framework, GPU rendering, synchronisation, etc. It’s not Flutter where everything is ‘flat’ with the maximum of technical costs involved.
So if you place 50 Views on a screen which do not even render anything, we will definitely have to face a pretty good FPS decrease.
Solution: Do not design visually-heavy screens when you work with the item lists.
Let’s give a closer look to how the time is usually spent from the very start you create a ViewHolder and up to rendering of the first frame.
- Inflate and View creation.
- Caching and synchronisation of the created View and ViewHolder.
- Data binding and rendering.
I would like you to pay closer attention to items 1 and 3.
- The most costly is item 1 — View creation out of xml-based layout.
- Efficiency of item 3 depends on the availability of data ready for rendering. For example, if you want to show some text, the only thing you should do in bind is to assign values from the model. The rest of the calculations should be done outside ViewHolder.
textUserName.text = userItem.name
Types of Views used in ViewHolder(s) as well as heavy UI tools, such as PorterDuff and Shader subclasses, etc. also have a great deal of importance.
Solution: xml-based layout should be maximally flat and include a minimum of heavy elements to render.
A number of unique ViewHolder(s)
As was previously described the heavier the xml, the more time you need to process it. So, if a screen shows a number of heavy ViewHolder(s), even with the Diffutils used, lagging is inevitable.
Solution: keep balance of visible ViewHolder(s) on one screen depending on their ‘weight’. Or add 1 list item at a time using Diffutils or manually.
Animation inside ViewHolder and outside RecyclerView
Animation is ‘heavy’ in all respects. So, its use in ViewHolder should make a maximum sense and be optimised, since it consumes a hefty part of processing time.
Solution: start of animation should always correspond to the appearance of View on a screen whereas its end to its disappearance or to the time a user starts working with a list (scroll, data update).
One of the interesting items of the list. RecyclerView lets you optimise the work with a list depending on the situation.
- Diffutils. List data update. The utility will calculate the data diff as effective as possible and will render it with animation.
- setHasFixedSize(…). You should always use it when the size of RecyclerView doesn’t change. It allows to make use of the internal optimisations to render the list items.
- setItemViewCacheSize(…). Optimisation allows to set a quantity of View(s) outside the screen which might possibly be used in the recycledViewPool
- setRecycledViewPool(…). Optimisation allows to reuse similar View(s) between the nested RecyclerView(s).
And now let’s move to the list of factors which might spoil our efforts.
If you try to very very slowly scroll the list in the Debug GPU mode, you will see the item list jitter. The reason for it is a heavy process of gesture recognition as well as the I/O utilisation.
Having tested lots of different item lists on flagship and very slow smartphone models with stock Android on board we can conclude that everything is not that demonstrably clear. For example, the simplest item lists work in a better way on Android 5 rather than on Android 9+. It seems like such a behaviour is there due to Garbage Collector ‘aggressiveness’ or specific implementation.
Relying on comparison of Qualcomm Snapdragon chipsets there seems to be a direct correlation between their processing power and its generation. So, we can presume that obsolete hardware will hardly be conducive to item lists ‘agility’.
Utilised capacity of the device
The most unpleasant case of performance loss. With the I/O being overloaded all our efforts will prove useless and bring the performance to nought. Let’s have a closer look at the basic cases:
- Intensive utilisation of the drive.
- CPU utilisation by some separate process.
- GPU utilisation (for example, rendering in background)
- GPS, 5G on
- Gesture recognition
All this can be ascribed to Android’s imperfection as a system if we compare similar cases with iOS where the app in the background is given the least priority.
Below is the result of testing of RecyclerView on different devices as well as the initial data in the HWUI mode. The test can not guarantee 100% true results, yet its results reflect my previous work experience as to using item lists and their peculiarities.
Samsung S10 Android 10
Pixel 2 Android 11
LG K7 Android 5
As follows from the test results, the factors influencing the performance of RecyclerView depend heavily on the items described in group 2.
What is the bottomline?
No doubt, RecyclerView is the best solution to work with item lists bearing in mind that there are a whole lot of Android-based devices as well as customised Android versions which it can be used for. Nevertheless, as our experience shows a user has to sweat and toil to utilise this component to the max. You should follow the above recommendations and your item lists will work as fast as they possibly could.
Should you be interested in the topic as such, you can also check out one of my previous articles. Also, feel free to subscribe to my TG channel GMD. It’s all about mobile app development, my experiences, using different tools and devices.