性能和视图层次结构

管理View 对象的层次结构的方式会显著影响应用的性能。此页面介绍如何评估视图层次结构是否导致应用速度变慢,并提供一些解决可能出现的问题的策略。

本页面重点介绍如何优化基于 View 的布局。有关如何优化 Jetpack Compose 性能的信息,请参阅 Jetpack Compose 性能

布局和测量性能

渲染管道包括一个布局和测量阶段,在此阶段,系统会适当地将相关项放置在您的视图层次结构中。此阶段的测量部分确定 View 对象的大小和边界。布局部分确定在屏幕上的哪个位置放置 View 对象。

这两个管道阶段都会对它们处理的每个视图或布局产生一些小的开销。大多数情况下,此开销微不足道,不会明显影响性能。但是,当应用添加或删除 View 对象时,例如当 RecyclerView 对象回收或重用它们时,此开销可能会更大。如果 View 对象需要考虑调整大小以满足其约束,则开销也可能更高。例如,如果您的应用在包装文本的 View 对象上调用 SetText(),则 View 可能需要调整大小。

如果此类情况花费的时间过长,则可能会阻止帧在允许的 16 毫秒内渲染,这会导致帧丢失并使动画变得卡顿。

由于您无法将这些操作移动到工作线程(您的应用必须在主线程上处理它们),因此最好对其进行优化,使其花费的时间尽可能少。

管理复杂布局

Android 布局 允许您在视图层次结构中嵌套 UI 对象。这种嵌套也会带来布局开销。当您的应用处理一个对象的布局时,应用还会对布局的所有子项执行相同的处理。

对于复杂的布局,有时开销只在系统第一次计算布局时才会出现。例如,当您的应用在 RecyclerView 对象中回收复杂的列表项时,系统需要布置所有对象。在另一个示例中,微不足道的更改可能会向上级联传播到父级,直到它们到达不影响父级大小的对象。

布局花费时间过长的一个常见原因是 View 对象的层次结构相互嵌套。每个嵌套的布局对象都会增加布局阶段的开销。层次结构越扁平,布局阶段完成所需的时间就越少。

我们建议使用 布局编辑器 创建 ConstraintLayout,而不是 RelativeLayoutLinearLayout,因为它通常更高效且减少了布局的嵌套。但是,对于可以使用 FrameLayout 实现的简单布局,我们建议使用 FrameLayout

如果您使用的是 RelativeLayout 类,则可以使用嵌套的、未加权的 LinearLayout 视图来实现相同的效果,但成本更低。但是,如果您使用的是嵌套的、加权的 LinearLayout 视图,则布局成本会高得多,因为它需要多次布局传递,如下一节所述。

我们还建议使用 RecyclerView 而不是 ListView,因为它可以回收单个列表项的布局,这既更高效,又可以提高滚动性能。

双重征税

通常,框架会以单次传递执行布局或测量阶段。但是,在某些复杂的布局情况下,框架可能需要多次迭代需要多次传递才能解决的层次结构的一部分,才能最终定位元素。不得不执行多次布局和测量迭代被称为双重征税

例如,当您使用 RelativeLayout 容器时,它允许您相对于其他 View 对象的位置来定位 View 对象,框架会执行以下顺序

  1. 执行布局和测量传递,在此期间,框架会根据每个子项的请求计算每个子项的位置和大小。
  2. 使用此数据,并考虑对象权重,以确定相关视图的正确位置。
  3. 执行第二次布局传递以完成对象的定位。
  4. 转到渲染过程的下一阶段。

视图层次结构的层级越多,性能损失的可能性就越大。

如前所述,ConstraintLayout 通常比其他布局(除了 FrameLayout)更有效率。它不太容易出现多次布局传递,并且在许多情况下无需嵌套布局。

RelativeLayout 之外的其他容器也可能会增加双重征税。例如

  • 如果您将 LinearLayout 视图设置为水平方向,则可能会导致两次布局和测量传递。如果添加了 measureWithLargestChild,则在垂直方向上也可能会发生两次布局和测量传递,在这种情况下,框架可能需要执行第二次传递以解决对象的正确大小。
  • GridLayout 也允许相对定位,但它通常通过预处理子视图之间的位置关系来避免双重征税。但是,如果布局使用权重或使用 Gravity 类填充,则预处理的优势就会丧失,如果容器是 RelativeLayout,则框架可能需要执行多次传递。

多次布局和测量传递不一定是性能负担。但是,如果它们出现在错误的位置,则可能会成为负担。请注意以下条件之一适用于您的容器的情况

  • 它是视图层次结构中的根元素。
  • 它下方有一个深层次的视图层次结构。
  • 它有很多实例填充屏幕,类似于 ListView 对象中的子项。

诊断视图层次结构问题

布局性能是一个具有许多方面的复杂问题。以下工具可以帮助您确定性能瓶颈发生的位置。某些工具提供的的信息不太明确,但可以提供有用的提示。

Perfetto

Perfetto 是一款提供性能数据的工具。您可以在 Perfetto UI 中打开 Android 跟踪。

分析 GPU 渲染

设备上的 分析 GPU 渲染 工具(适用于 Android 6.0(API 级别 23)及更高版本的设备)可以为您提供有关性能瓶颈的具体信息。此工具可让您查看布局和测量阶段对 每个渲染帧 耗时多长。这些数据可以帮助您诊断运行时性能问题,并帮助您确定需要解决哪些布局和测量问题。

在其捕获数据的图形表示中,分析 GPU 渲染使用蓝色表示布局时间。有关如何使用此工具的更多信息,请参阅 分析 GPU 渲染速度

Lint

Android Studio 的 Lint 工具可以帮助您了解视图层次结构中的效率低下之处。要使用此工具,请选择分析 > 检查代码,如图 1 所示。

图 1. 在 Android Studio 中选择检查代码

有关各种布局项的信息显示在Android > Lint > 性能下。要查看更多详细信息,请单击每个项目以展开它并在屏幕右侧的面板中显示更多信息。图 2 显示了已展开信息的示例。

图 2. 查看 Lint 工具识别到的特定问题的信息。

单击某个项目会在右侧的面板中显示与该项目关联的问题。

要了解有关此领域特定主题和问题的更多信息,请参阅 Lint 文档。

布局检查器

Android Studio 的 布局检查器 工具提供了应用视图层次结构的可视化表示。它是浏览应用层次结构的好方法,提供了特定视图的父链的清晰可视化表示,并允许您检查应用构建的布局。

布局检查器呈现的视图还可以帮助识别因双重征税而导致的性能问题。它还可以提供一种方法来识别嵌套布局的深链或具有大量嵌套子项的布局区域,这可能是性能成本的来源。在这些情况下,布局和测量阶段可能代价高昂并导致性能问题。

有关更多信息,请参阅 使用布局检查器和布局验证调试布局

解决视图层次结构问题

解决由视图层次结构引起的性能问题的基本概念在实践中可能很困难。防止视图层次结构造成性能损失包括扁平化视图层次结构和减少双重征税。本节讨论了实现这些目标的策略。

删除冗余的嵌套布局

ConstraintLayout 是一个 Jetpack 库,它具有大量用于在布局中定位视图的不同机制。这减少了嵌套一个 ConstaintLayout 的需要,并有助于扁平化视图层次结构。与其他布局类型相比,使用 ConstraintLayout 扁平化层次结构通常更简单。

开发人员通常使用的嵌套布局多于必要。例如,RelativeLayout 容器可能包含一个也是 RelativeLayout 容器的单个子项。这种嵌套是冗余的,会增加视图层次结构的不必要的开销。Lint 可以为您标记此问题,从而减少调试时间。

采用合并或包含

冗余嵌套布局的一个常见原因是 <include> 标签。例如,您可以定义一个可重用的布局,如下所示

<LinearLayout>
    <!-- some stuff here -->
</LinearLayout>

然后,您可以添加一个 <include> 标签以将以下项目添加到父容器

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/app_bg"
    android:gravity="center_horizontal">

    <include layout="@layout/titlebar"/>

    <TextView android:layout_width="match_parent"
              android:layout_height="wrap_content"
              android:text="@string/hello"
              android:padding="10dp" />

    ...

</LinearLayout>

前面的包含不必要地将第一个布局嵌套在第二个布局中。

<merge> 标签可以帮助防止此问题。有关此标签的信息,请参阅 使用 <merge> 标签

采用更便宜的布局

您可能无法调整现有的布局方案以使其不包含冗余布局。在某些情况下,唯一的解决方案可能是通过切换到完全不同的布局类型来扁平化您的层次结构。

例如,您可能会发现TableLayout提供了与具有许多位置依赖关系的更复杂布局相同的功能。Jetpack 库ConstraintLayout提供了与RelativeLayout类似的功能,以及更多有助于创建更扁平、更高效布局的功能。