性能和视图层次结构

您管理 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,则在垂直方向上也可能会出现两次布局和测量传递,在这种情况下,框架可能需要进行第二次传递才能解决对象的正确大小。
  • The 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>

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

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

采用更便宜的布局

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

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