第一次滚动时在 RecyclerView 中滞后

Lagging in RecyclerView at very first scrolling

我显示设备的联系人列表。在我的 Android 版本 9 的 Samsung Galaxy S8 中,当我第一次滚动 recyclerView 时,它不流畅并且有点滞后。但随后它开始非常流畅地滚动。如果我使用后退按钮关闭应用程序并再次启动该应用程序,它会再次平滑滚动,但是如果我从最近的历史记录中销毁应用程序实例,然后再次启动该应用程序,它并不平滑并且在第一次滚动时有点滞后。 (我已经在 Google Pixel2 中进行了测试,当黄油含量低时,我感觉和我解释的一样滞后。)

这是 Galaxy s8 中问题的录制屏幕:https://drive.google.com/file/d/1szfF1oKEYZK3LIqQHC-MsapRxdJ85bFy/view?usp=sharing

我尽可能地优化了recyclerView适配器,看来问题与我的适配器无关。您可以在此处查看源代码:https://github.com/AliRezaeiii/DignityContacts

我有一个自定义的 CoordinatorLayout.Behavior 到 hide/show AppBarLayout 以及 bottomBar。我确信它与此无关,因为当没有 CoordinatorLayout 行为时,它会再次显示滞后,正如我上面解释的那样。

这是我的 recyclerView 项目布局:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" 
    android:layout_height="wrap_content" 
    android:background="@android:color/white" 
    android:orientation="vertical">
    
    <include layout="@layout/contact_separator" />
    
    <include layout="@layout/contact_detail" />
    
    <LinearLayout
        android:id="@+id/subItem"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="vertical" />
    
    </LinearLayout>

contact_separator 和 contact_detail 都是 ConstaintLayouts。

这是我设置 recyclerView 的方式:

mAdapter = new ContactsAdapter();
binding.recyclerView.setHasFixedSize(true);
binding.recyclerView.setAdapter(mAdapter);

final Observer<Resource<List<Contact>>> contactsObserver = resource -> {
            if (resource instanceof Resource.Success) {
                mContacts = ((Resource.Success<List<Contact>>) resource).getData();
                mAdapter.setItems(mContacts, true);
            }
        };

这可能是什么原因?

如果没有项目 运行 很难分辨,但快速查看(只需 2-3 分钟浏览您的适配器代码),就会有一些“味道”。

  1. 您使用的是普通 RecyclerView.Adapter,您 should/could 将 ListAdapter<T, K>DiffUtilCallback 一起使用以避免使用 notifyDataSetChanged()当您执行 setList(...) 时(相反,您只需执行 submitList(...) 并让适配器为您解决问题。

  2. 您的 bind(...) 方法相当复杂和冗长;有两个潜在的循环和视图参数更改(小部件尺寸),这将导致 至少 测量传递(随后是布局传递),所有这些都会发生在每个项目中视图持有者,当您提交列表时。创建应用程序时(在它被销毁之后),必须(再次)计算所有这些,因此回收器视图具有 setFixedSize = true(为什么你需要这个,考虑到你的适配器,这不是真的)。如果 fixed Size 为真,则 避免 调用 requestLayout() 但这意味着您告诉适配器它的大小是恒定的(这意味着它可以 grow/shrink 基于项目计数,但所有项目都具有相同的大小)。这是一种优化,但它会反咬你一口,而且很可能不是你想要的。

  3. 您的 flagItems 是一个 LinearLayout,您 add/remove 在运行时在绑定方法中动态查看此线性布局。

  4. 你对 subItem(另一个 LinearLayout)做同样的事情。

  5. 然后又另一个循环(联系电话)的可能性,其中可能再次修改布局(更改约束集)。

所有这一切(还有一些其他细节)但现在就可以了……在适配器方面是“危险信号”。

有趣的是,所有这一切都应该在 比 android 渲染一帧 (60 毫秒?)更少的时间内完成。因此,您要求代码完成 大量工作 并触发大量副作用,并期望它在 60 毫秒或更短的时间内完成。这适用于每个被绑定的视图(取决于设备的屏幕尺寸),所以想象一下屏幕上有 5 个项目; RecyclerView 会提前绑定更多,为滚动做好准备。

你能做什么?

首先我要退一步看你的List<Contact> contacts。这些联系人可以更好地 转换 以在 RecyclerView 上显示。所有关于 what/how 的逻辑都应该提前解决,并以 flattest/simplest 可能的形式呈现给适配器(它已经有其他工作要做)。您可以利用 ViewHolders 的 type 发挥自己的优势,方法是将您的数据分解为不同的类型,并让适配器简单地为每种视图类型绑定正确的 viewHolder。

因此,您需要提供更适合 viewHolder 的 List<SomeOtherObject>,而不是 List<Contact>,并且只包含稍后重新获取原始数据所需的数据 Contact(如果需要),或重建它。

然后您可以简化并删除所有 logic/decision 从 viewHolder#bind 方法中生成的内容,因为当您确定要绑定的数据类型时,很多问题都已解决。

我将从这里开始,因为您可以尝试优化所有您想要的,但您仍然要求适配器为您完成所有这些工作。

为什么当您“不终止应用程序”时不会发生这种情况?

那是因为(我估计)这些东西是缓存和预先计算的(他们第一次已经花时间了),所以因为你有 FixedSize = true,RecyclerView 知道(已被告知)每个 ViewHolder 都不会改变,所以不需要重新计算它。

简而言之,你并没有让你的适配器更高效,你只是告诉他花那么多时间一次。 ;)

你应该用一个列表初始化适配器,目前你正在设置一个观察者,当它被观察到时,列表就会被初始化,这就是它滞后的原因。