An experimental project aims to reduce the lag when starting to scroll
The original project link: https://github.com/rockerhieu/emojicon
Methods I have tested in the project:
- Original grid: the unchanged version from the original project
- Normal grid view: a small changed from the original version, using normal TextView instead of the EmojiTextView.
- Using multithreads: load all emojis in another thread before showing them in UI. This method produces glitches when scrolling and even not displaying correctly in some cases - definitely an unusable method.
- Using caches: load all emojis ahead of time by virtually drawing those emojis in an off-screen canvas. This significantly reduces the cpu processing time in the custom version of emojis, but not very efficient with the system default one.
- Static layout: this is similar to the normal GridView method, but replacing normal TextView with StaticLayout (a more detailed explanation can be found here). This delivers impressive result, compared to other methods.
- RecyclerView with GridLayout: substitute GridView with a RecyclerView, not very promising result, even lagger than the original one.
- Vertical recyclerView: same as above.
- TableLayout: substitute GridView with TableLayout. This is the only method satifies the expectations of the experiment. It is not perfect, of course, the major drawback is all emojis have to be added initially, negatively affecting user experience from the start and not really scalable for future development.
- Litho: use the open-source framework from Facebook which claims to improve the scrolling performance greatly by moving the measure and layout off the main thread, flattening views, recycling primitive views. Nevertheless, the result is actually the worst in all methods, unbearable lags in every scroll gestures.
To demonstrate the performance of each method, the experiment will measure correlated processing time using TraceView from Android Device Monitor. These numbers are only attempted for experimental purposes, they are not applicable to all devices. Please take these results with a grain of salt.
Some clarifications:
- Incl stands for inclusive which is the time spent on the function itself as well as the sum of the times of all functions that it calls. Wee also have the exclusive keyword which represents only the time spent on function itself. The exclusive time is kind of irrelevant to this context, so it is excluded from the results.
- The experiment only focuses on the scrolling performance, other factors e.g. initial views time,... are not considered in this project.
- The measured functions for each methods are slightly different according to their underlying structures (e.g. GridView, RecyclerView). For those ones use GridView, the chosen function is getView (except for the StaticLayout case will use both getView and TextLayoutView$onDraw). And getViewForPosition will be chosen for RecyclerView, this also includes Litho framework (yup Litho is based on RecyclerView foundation).
- The experiment also excludes the multithreading method. Multithreading in this situation is basically a failed attempt, except for the initial stage, all parts after that are still happening in the UI thread. The results will be identical to normal GridView with no performance gain, maybe even worse in some cases.
Method | Incl CPU time | Incl CPU time | Incl Real time | Incl Real time | Calls+Recur | CPU time/cal | Real time/cal |
---|---|---|---|---|---|---|---|
Original | 42.2% | 74.327 | 3.2% | 105.140 | 27.4 | 2.723 | 3.845 |
Normal GridView | 35.7% | 55.130 | 2.5% | 75.522 | 19.2 | 2.920 | 3.997 |
Caches | 44.3% | 77.473 | 3.5% | 111.002 | 27.1 | 2.934 | 4.202 |
StaticLayout | 10.4% | 11.073 | 0.7% | 20.478 | |||
RV with GridLayout | 38.8% | 126.047 | 3.6% | 161.503 | 53.0 | 2.390 | 3.063 |
Vertical RV | 23.1% | 63.302 | 2.3% | 89.397 | 24.6 | 2.638 | 3.702 |
Litho | 12.1% | 47.438 | 1.2% | 60.667 | 30.0 | 1.581 | 2.018 |
*This table only includes the average results, you can find more comprehensive results here
**Experiment is conducted with the system default emojis (not custom emojis) in GenyMotion emulator (Google Pixel 7.1.0)
As shown in the table, the difference between the first 3 methods (original, normal GV, caches) is negligible. The normal GV method gains a small boost in processing time, thanks to the replacement with the simple TextView from the original. The cached mechanism, however, does absolutely nothing to performance. Compared to them, the StaticLayout result is so much more compelling, with really nothing to debate.
On the other hand, there's a large discrepancy between those methods built with RV under the hood. Grid RV (aka the slowest) takes as much as two times longer than the fastest method, which is the Litho framework. One surprising thing is that the fancy asynchronous layout benefit was actuall not in used when scrolling, only the initialize view process includes this feature.
Just looking at the processing time, we might think that the performance in RVs will be very much alike to GVs, but this is actually an inaccurate way to compare those two. All the numbers have to be considered as a whole to give a more proper evaluation. Even though the processing time is nearly equal, the occupied percentage in RVs is much lower than the other, i.e. the total amount of time is much longer in comparison. This also matches with the reality when tested with a low-end device, RV type (especially the Litho framework) actually gives a serious lag when scrolling, something does not happen noticeably with GVs.
Last but not least, these numbers are just for experimental purposes, in other words smaller processing time will not guarantee better performance. Do not use this as a reference for performance in any kind of debates or papers.