RecyclerView将主线程挂起并包含许多元素

时间:2019-06-04 23:13:09

标签: android android-recyclerview

我有一个RecyclerView,其适配器基于List个项目。当适配器中有约400个元素时,在通知适配器已设置新数据时,主线程挂了大约1-2秒。

我已经跟踪了它,并且它只是在视图层次结构中很忙(从ViewRootImpl.performTraversal()开始)。这没有任何意义,因为我希望它只是渲染实际上可见的10 ish元素,应该很快。

我是否缺少RecyclerView的某些方面?

编辑:正在为列表中的每个元素调用onBindViewHolder()。这似乎不正确。

编辑2: 这是适配器类: https://pastebin.com/8a2yLprh

这是项目布局: https://pastebin.com/J0DtnKwB

编辑3: 如果我给RecyclerView一个静态高度,就可以解决问题。本身不是解决方案,但希望能指明方向。

3 个答案:

答案 0 :(得分:1)

我看到了一些问题,我将以不特定的顺序指出它们;尽管我还没有发现可能导致您问题的任何具体信息,但在编写此答案时,我可能会发现一些问题,因此让我们分析您的代码。

缺少什么?

您没有包含更多代码,因此我不知道如何使用此适配器,或者recyclerview布局如何(它是否包含在另一个滚动视图或嵌套滚动视图中?) 这些是重要的问题,因为它可能会影响Android渲染/布局引擎的工作方式。

布局

您正在使用带有权重的LinearLayout(此处不需要)。除非您有充分的理由,否则不建议使用这些方法。计算窗口小部件的大小以及不计算窗口小部件的大小是很昂贵的,并且在涉及图像和位图时更是如此。

我建议您切换到ConstraintLayout并做正确的事。您将感谢您,您的布局可能会表现更好(根据我的观察,ConstraintLayout的当前布局是不可能的)。

适配器

  • 这很奇怪:
    @Override
    public void onClick(View v) {
      Listener l;
      if ((l = listener) != null) {
        mainHandler.post(() -> l.onItemClick(devices.get(getAdapterPosition())));
      }
    }

总是应在主线程上进行单击,而无需发布任何内容。 我会这样写:

if (listener instanceof Listener) { //already fails if L is null anyway) 
     listener.onItemClick(...)
}
  • 您在onBind中执行了大量查找,如果您缓存了这些循环,则可以节省循环。例如:
Picasso.with(context).load(SetupAppUtils.getDeviceImageUrl(context, device)).into(image);

我不知道您的SsetupAppUtils.getDeviceImageUrl方法做什么,但是它可能很昂贵,并且您可以根据需要将其保存在Map中(一次查询URL,重复使用)(仅是个主意,无法分辨)无需查看代码)

  • 您似乎在onBind方法中嵌入了许多业务逻辑(很多if语句通常是一个危险信号。您的适配器对此一点也不在乎,它只是“从模型->视图中调整数据,如果可以避免,则不应做出决定并执行转换。在这种情况下,我看到您通过了Device ...似乎还可以,但是您似乎在做很多事情关于检查和转换,请记住,一旦绑定所有项目,这都会发生……,每次用户滚动并需要绑定新项目时,只要需要,您就再次执行所有这些工作来“重新绑定”视图持有者。

  • 您要通知完整的更改,这意味着要为recyclerview分配一个批号(必须重新测量并重新布置),因为您实际上是在告诉适配器:所有数据都已更改。 / p>

  void setDevices(List<Device> devices) {
    this.devices.clear();
    this.devices.addAll(devices);
    notifyDataSetChanged();
  }

使用DiffUtil(包含在框架中!)非常简单,因此完全推荐使用。

  • 您覆盖了一个方法,但是所有项目都具有相同的ID:
  @Override
  public long getItemId(int i) {
    return 0;
  }

如果您无法提供良好的ID,请不要覆盖此ID。我将返回item [i] .something()(在此之前检查null /空列表!)。还要将i重命名为position,因为这样就可以了。

还有其他吗?

在这些方面,我看不出有什么超级奇怪的。我将做一个简单的测试:删除那里所有的绑定代码(onBind中的所有代码),然后放入name.SetText(...),看看是否仍然看到“滞后”。 如果不这样做,那么您就会知道其中一些操作所花费的时间比其他操作要长。

尝试较少的物品,看看会发生什么;一个recyclerview应该只绑定可见的视图+/-几个上/下,而不是一次绑定400个项目。 我能想到的唯一原因是,如果您的RecyclerView位于NestedScrollView或类似的容器内。

答案 1 :(得分:0)

要一次加载的400个项目太多,您应该使用 Pagination 机制。Google现在将paging library作为android Jetpack的一部分。另外,您会发现很多文章和教程,讨论如何在android中实现它。

答案 2 :(得分:0)

正在回答我自己的问题(带有一些补充评论)...不幸的是,我仍然不完全了解其根本原因。当我将RecyclerView的高度设置为固定的值(例如"400dp")时,问题自行解决。显然,尽管这不是一个适当的解决方案,但它使我开始了。

我尝试的任何布局都不能解决问题。最终,我将所有内容都转换为使用ConstraintLayout,并且在对布局的宽度和高度进行调整之后,我发现了可以解决该问题的方法。这与使用"0dp"作为高度有关。不好意思我不了解根本原因。我在下面粘贴了布局,以防它对任何人都有帮助。

总的来说,我对ConstraintLayout感到不寒而栗。这非常冗长,并且约束范式(相对于我想您称之为遏制)并不自然。例如,需要使用浮动Group元素来显示和隐藏其他逻辑组,并使用“链”将元素组居中。

具有讽刺意味的是,在旧学校LinearLayout上一切正常。我将其作为学习活动进行。您必须怀疑使用传统的LinearLayout + ListViewConstraintLayout + RecyclerView的平均布局对真正的性能影响是什么。

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:tools="http://schemas.android.com/tools"
  xmlns:app="http://schemas.android.com/apk/res-auto"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  android:gravity="center">

  <ProgressBar
    android:id="@+id/progress_bar"
    style="@android:style/Widget.Holo.ProgressBar.Large"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="center"
    android:visibility="visible"
    app:layout_constraintTop_toTopOf="parent"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintLeft_toLeftOf="parent"
    app:layout_constraintRight_toRightOf="parent"
    />

  <android.support.constraint.Group
    android:id="@+id/content_layout"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:orientation="vertical"
    android:visibility="gone"
    tools:visibility="visible"
    app:constraint_referenced_ids="header,list_view"
    />

  <android.support.constraint.ConstraintLayout
    android:id="@+id/header"
    android:layout_width="0dp"
    android:layout_height="48dp"
    app:layout_constrainedHeight="true"
    android:background="@color/gray_20"
    app:layout_constraintTop_toTopOf="parent"
    app:layout_constraintBottom_toTopOf="@id/list_view"
    app:layout_constraintLeft_toLeftOf="parent"
    app:layout_constraintRight_toRightOf="parent"
    >

    <View
      android:id="@+id/device_image"
      android:layout_width="0dp"
      android:layout_height="0dp"
      app:layout_constraintBottom_toBottomOf="parent"
      app:layout_constraintHorizontal_weight="1"
      app:layout_constraintLeft_toLeftOf="parent"
      app:layout_constraintRight_toLeftOf="@id/device_name"
      app:layout_constraintTop_toTopOf="parent" />

    <TextView
      android:id="@+id/device_name"
      android:layout_width="0dp"
      android:layout_height="wrap_content"
      android:text="@string/name_label"
      app:layout_constraintBottom_toBottomOf="parent"
      app:layout_constraintHorizontal_weight="2"
      app:layout_constraintLeft_toRightOf="@id/device_image"
      app:layout_constraintRight_toLeftOf="@id/device_model"
      app:layout_constraintTop_toTopOf="parent" />

    <TextView
      android:id="@+id/device_model"
      android:layout_width="0dp"
      android:layout_height="wrap_content"
      android:text="@string/model_label"
      app:layout_constraintBottom_toBottomOf="parent"
      app:layout_constraintHorizontal_weight="2"
      app:layout_constraintLeft_toRightOf="@id/device_name"
      app:layout_constraintRight_toLeftOf="@id/device_id"
      app:layout_constraintTop_toTopOf="parent" />

    <TextView
      android:id="@+id/device_id"
      android:layout_width="0dp"
      android:layout_height="wrap_content"
      android:text="@string/id_label"
      app:layout_constraintBottom_toBottomOf="parent"
      app:layout_constraintHorizontal_weight="2"
      app:layout_constraintLeft_toRightOf="@id/device_model"
      app:layout_constraintRight_toRightOf="parent"
      app:layout_constraintTop_toTopOf="parent" />
  </android.support.constraint.ConstraintLayout>

  <android.support.v7.widget.RecyclerView
    android:id="@+id/list_view"
    android:layout_width="0dp"
    android:layout_height="0dp"
    android:fadingEdgeLength="@dimen/fadingEdgeLength"
    android:footerDividersEnabled="false"
    android:requiresFadingEdge="vertical"
    app:layout_constraintTop_toBottomOf="@id/header"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintLeft_toLeftOf="parent"
    app:layout_constraintRight_toRightOf="parent"
    />
</android.support.constraint.ConstraintLayout><?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:tools="http://schemas.android.com/tools"
  xmlns:app="http://schemas.android.com/apk/res-auto"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  android:gravity="center">

  <ProgressBar
    android:id="@+id/progress_bar"
    style="@android:style/Widget.Holo.ProgressBar.Large"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="center"
    android:visibility="visible"
    app:layout_constraintTop_toTopOf="parent"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintLeft_toLeftOf="parent"
    app:layout_constraintRight_toRightOf="parent"
    />

  <android.support.constraint.Group
    android:id="@+id/content_layout"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:orientation="vertical"
    android:visibility="gone"
    tools:visibility="visible"
    app:constraint_referenced_ids="header,list_view"
    />

  <android.support.constraint.ConstraintLayout
    android:id="@+id/header"
    android:layout_width="0dp"
    android:layout_height="48dp"
    app:layout_constrainedHeight="true"
    android:background="@color/gray_20"
    app:layout_constraintTop_toTopOf="parent"
    app:layout_constraintBottom_toTopOf="@id/list_view"
    app:layout_constraintLeft_toLeftOf="parent"
    app:layout_constraintRight_toRightOf="parent"
    >

    <View
      android:id="@+id/device_image"
      android:layout_width="0dp"
      android:layout_height="0dp"
      app:layout_constraintBottom_toBottomOf="parent"
      app:layout_constraintHorizontal_weight="1"
      app:layout_constraintLeft_toLeftOf="parent"
      app:layout_constraintRight_toLeftOf="@id/device_name"
      app:layout_constraintTop_toTopOf="parent" />

    <TextView
      android:id="@+id/device_name"
      android:layout_width="0dp"
      android:layout_height="wrap_content"
      android:text="@string/name_label"
      app:layout_constraintBottom_toBottomOf="parent"
      app:layout_constraintHorizontal_weight="2"
      app:layout_constraintLeft_toRightOf="@id/device_image"
      app:layout_constraintRight_toLeftOf="@id/device_model"
      app:layout_constraintTop_toTopOf="parent" />

    <TextView
      android:id="@+id/device_model"
      android:layout_width="0dp"
      android:layout_height="wrap_content"
      android:text="@string/model_label"
      app:layout_constraintBottom_toBottomOf="parent"
      app:layout_constraintHorizontal_weight="2"
      app:layout_constraintLeft_toRightOf="@id/device_name"
      app:layout_constraintRight_toLeftOf="@id/device_id"
      app:layout_constraintTop_toTopOf="parent" />

    <TextView
      android:id="@+id/device_id"
      android:layout_width="0dp"
      android:layout_height="wrap_content"
      android:text="@string/id_label"
      app:layout_constraintBottom_toBottomOf="parent"
      app:layout_constraintHorizontal_weight="2"
      app:layout_constraintLeft_toRightOf="@id/device_model"
      app:layout_constraintRight_toRightOf="parent"
      app:layout_constraintTop_toTopOf="parent" />
  </android.support.constraint.ConstraintLayout>

  <android.support.v7.widget.RecyclerView
    android:id="@+id/list_view"
    android:layout_width="0dp"
    android:layout_height="0dp"
    android:fadingEdgeLength="@dimen/fadingEdgeLength"
    android:footerDividersEnabled="false"
    android:requiresFadingEdge="vertical"
    app:layout_constraintTop_toBottomOf="@id/header"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintLeft_toLeftOf="parent"
    app:layout_constraintRight_toRightOf="parent"
    />
</android.support.constraint.ConstraintLayout>