Search code examples
androidandroid-recyclerviewandroid-anr-dialogandroid-nestedscrollview

RecyclerView notifyDataSetChanged() freezes UI without ANR


We have a NestedScrollView that contains two different RecyclerView both working with vertical scroll. The scroll layout lays inside SwipeRefreshLayout.

Upd: We know about getItemViewType(pos) method and use it in other places. But here we have complex logic that will be further divided into 2 different screens, therefore we have two separate presenters for each of RecyclerView and combining them into one and separating again in a month is not an option.

From some moment, we noticed that updating the screen using swipe to refresh starts to freeze the UI: skipped frames in logs, impossibility to make any operations with UI, freezing progress bar. It takes up to five seconds, but we don't get ANR. It works well in the beginning and freezes at the end. If we remove notifyDataSetChanged() in adapter, everything looks good.

We tried to remove one of recycler views, to remove all the operations from ViewHolder.onCreate() and onBindViewHolder() but it didn’t help. Also, we cross-checked if the problems come from some data processing (we use RxJava 2 to manipulate threading) and we see that all operations with networking, db and data processing are made in non-UI thread and are already finished to the moment when lag starts.

Here is the layout:

<android.support.v4.widget.SwipeRefreshLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/swipe_refresh"
    android:layout_width="match_parent"
    android:layout_height=«match_parent"
    android:orientation="vertical"
    >
<android.support.v4.widget.NestedScrollView
    android:layout_width="match_parent"
    android:layout_height="wrap_content»
    >

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation=«vertical"
        android:paddingBottom="@dimen/spacing_bigger»>
        <android.support.v7.widget.RecyclerView
            android:id="@+id/recycle_chats"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:background="@color/white_bg"
            />

        <android.support.v7.widget.RecyclerView
            android:id="@+id/recycle_friends"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:background="@color/white_bg"
            />

    </LinearLayout>

</android.support.v4.widget.NestedScrollView>
</android.support.v4.widget.SwipeRefreshLayout>

Solution

  • It looks like NestedScrollView with layout_height="wrap_content" breaks all the optimizations of children RecyclerViews. It gives them infinite space regardless of the constraints from the parent view (SwipeRefreshLayout) that has layout_height="match_parent". As a result, RecyclerView creates view holders for all the models in adapter at once. Logging shows, that it takes 20-60ms for each of them what brings 2-5 second lag for 100 rows.

    We added logging to onCreateViewHolder() and noticed, that there are tens (almost a hundred) of calls to it, whereas there is only about ten elements on the screen. Changing NestedScrollView's layout_height to "match_parent" fixed the problem.