Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Android: Jank on initial scroll with RecyclerView in debug

I have a simple test application which has a social media-like layout: enter image description here

It consists of an outer recycler view for posts and nested RV for comments (with indentation on the screenshot).

The issue: Mostly in debug mode, there's a considerable jank only when the initial scroll happens (probably some initialization stuff of RVs). Further scrolling is smooth both in debug and release. In release, it happens mostly after usage of some intensive apps, e.g. watching videos on Facebook app. And the jank is less noticeable if it happens in release. Systrace when the initial scroll happens

Setup details

Phone - Nexus 5X with Android 8.1, compileSdkVersion - 28, minSdkVersion - 16, targetSdkVersion - 28

The relevant pieces of code are as follows:

The outer RV's adapter - here I'm using the posts data with their comments and also the nested RV pool so that nested RVs can take advantage of that. The view holder just stores some references to different controls in the view. onCreateViewHolder is the place where I tried some optimizations, including pre-creating 10 view holders (the current code). I've set setHasFixedSize to true, but that didn't help. The layout manager for the RV is LinearLayoutManager without any customization.

if (viewHolders.size == 0) {
        //Log.w("msg", "creating all view holders")
        for (i in 1..10) {
            val v = LayoutInflater.from(parent.context)
                    .inflate(R.layout.wallpost_view, parent, false) as LinearLayout
            val viewHolder = MyViewHolder(v, context, nestedViewsPool)
            viewHolders.push(viewHolder)
        }
    }

The inner RV's adapter - nothing special here as it currently doesn't contain any custom events.

Outer RV item's view - Yes, it uses nested layouts, but I didn't see any performance difference when coverting it to ConstraintLayout, CL version here.

Inner RV item's view - Nothing special here, looks similar to the outer item's view, but without buttons and links. CL version here

What can cause the jank? It's been there (in debug) since I initialy tested even with simplified views and code.

like image 370
Rosen Dimov Avatar asked Jul 09 '20 17:07

Rosen Dimov


2 Answers

A few things to take care of

1- do not measure performance in debug mode

I can't emphasize this enough. Run without debugger attached and you will see drastic performance improvements.

2- flatten your design

Instead of having RV where each item has a RV, use a single RecyclerView with different cells. So 1st cell would be "content of wallpost", 2nd would be "wallpost answer 1", 3rd "wallpost answer 2" etc.

I've been struggling hard to make one of my screens perform better where we have horizontal RV (viewPager 2 to be specific) with nested vertical RV, and I chased many dead ends, and ended up having to pull some tricks like disabling preload of previous/next page until the current page has been loaded. But it does NOT struggle after initial load.

3- Having <include in layouts is not likely to make it slower.

But having nested hierarchies does, like:

<LinearLayout> 
  <LinearLayout>
    <LinearLayout>

Consider rewriting everything as a single ConstraintLayout. Your UI is not that complicated. Delete a few things, measure it, and if it gets significantly better make the conversion. I'd start with the inner layout since there's potentially more of them in a single screen?

and last

4- be careful with date conversions

I noticed that there are some methods in ThreeTenAbp that consume an unreasonable amount of time. Try skipping all those methods replacing it with dummy strings, and check if it makes any difference. IF it does, you have a couple of options, but these are my 2 initial suspects

  • initialize date formatters only once, and use them in background thread
  • check your date parsers are running in a background thread

I wouldn't be surprised if Java 8 DateTime has the same issues.

================

After writing all of the above I took a look at your systrace. From looking quickly the culprit seems to be the nested linear layouts. I can see around 1.880 ms that there's a very long inflate taking 30ms, that's a big red flag. It has 3 linear layouts inside, which would tend to point in the general direction of either the inner or outer layout. So I'd give it a try at (3) first and if not enough I'd try (2).

============== Edit: adding (5)

5- sharing a RecycledViewPool for inner recyclerview

Create a single object of type RecycledViewPool and assign it to all your inner recyclerviews.
https://developer.android.com/reference/androidx/recyclerview/widget/RecyclerView.RecycledViewPool

like image 134
Fabio Avatar answered Sep 22 '22 05:09

Fabio


I'll post this as an answer since it does what I require - it reduces the time the first scroll takes.

I moved pre-creation of the two types of view holders in the calling activity:

for (i in 1..30) {
    val v = LayoutInflater.from(parent.context).inflate(R.layout.wallpost_view, parent, false) as LinearLayout
    val viewHolder = MyViewHolder(v, context, nestedViewsPool)
    viewHolders.push(viewHolder)
}

for (i in 1..20) {
    val v = LayoutInflater.from(this).inflate(R.layout.comment_view, null) as LinearLayout
    val viewHolder = WallpostsListAdapter.CommentsViewHolder(v)
    adapter.commentViewHolders.push(viewHolder)
}

This made the first scroll arround 2 times faster in most of the tests I conducted. And this action lead to the biggest gain in the optimization process.

I also adopted part of @Fabio's suggestions:

1. As I stated in the comments, I measure that stuff in debug mode to be able to better see the optimization results. In release, of course, the numbers in milliseconds will be much smaller (actually arround 10 times smaller from what I saw in the systrace in release).

2. This was the point that made the biggest difference, not huge difference but still it could be spotted in systrace. Check pastebins in the end of the answer for more info.

3. Indeed, Google recommends using ContraintLayout instead of nested layouts, but in my case this didn't make any difference. Actually nested linear layouts seemed to show slightly better resutls. My guess is that the views are not complex enough to see the gain with CL. I couldn't test the final optimized code with nested linear layouts, because I now use single multi-type view RV and they weren't working correctly with it, couldn't find the reason, but probably the gain wouldn't have been that significant.

4. Good point, but it didn't make any signifcant difference in my case, at least.

5. Obsolete as there is now only a single RV without nesting.

Relevant code

  • RV's adapter - now a multi-type view adapter, managing both posts and comments. The biggest change was that I needed to write mapping code between RV position and array index. Now on init I need to do that mapping. In comment's VH binding, I first need to get their parent post's RV position, from there the index in the array and finally from their difference to get the comment's index within post's comments array.
  • Post's view with ConstraintLayout
  • Comment's view with CL

Systraces

  • Views with CL, all view holders pre-created, single RV - here
  • Views with CL, only post's VH pre-created, single RV - here
like image 43
Rosen Dimov Avatar answered Sep 24 '22 05:09

Rosen Dimov