性能与视图层次结构

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

布局和度量性能

渲染流水线包含“布局和度量”阶段,系统在此阶段以适当的方式将相关项放置在视图层次结构中。此阶段的度量部分确定 View 对象的大小和边界,布局部分确定 View 对象在屏幕上的放置位置。

这两个流水线阶段在处理每个视图或布局时,都会产生少量开销。在大多数情况下,此开销很小,不会明显影响性能。不过,当应用添加或移除 View 对象时(例如在 RecyclerView 对象回收或重用 View 对象时),此开销会变大。如果 View 对象需要考虑调整大小来保持其约束,此开销也会增加:例如,如果您的应用对包围文本的 View 对象调用 SetText()View 可能需要调整大小。

如果这类操作花费的时间太长,可能会导致帧无法在允许的 16 毫秒内完成渲染,从而造成丢帧并使动画变得粗糙。

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

管理复杂性:布局很重要

Android 布局允许您将界面对象嵌套在视图层次结构中。这种嵌套也会增加布局开销。当应用处理布局对象时,也会对布局的所有子对象执行相同的处理。对于复杂的布局,有时仅在系统第一次计算布局时才会产生开销。例如,当应用在 RecyclerView 对象中回收复杂的列表项时,系统需要列出所有对象。又如,细微的更改可以往上朝父级传播,直至到达不影响父级大小的对象为止。

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

如果使用 RelativeLayout 类,则可通过使用未设权重的嵌套 LinearLayout 视图,以更低的开销达到同样的效果。此外,如果您的应用面向 Android 7.0(API 级别 24)或更高版本,您可以使用特殊的布局编辑器来创建 ConstraintLayout 对象,而非 RelativeLayout。这样做可以避免本节中讲到的许多问题。ConstraintLayout 类提供类似的布局控制,但性能大大提高。该类使用自己的约束解析系统,采用与标准布局完全不同的方式来解析视图之间的关系。

Double Taxation

通常情况下,框架会在一次遍历中快速执行布局或度量阶段。但在一些情况比较复杂的布局中,在最终放置元素之前,框架可能必须对层次结构中需要多次遍历才能解析的部分执行多次迭代。必须执行不止一次“布局和度量”迭代的情况称为“Double Taxation”。

例如,当您使用 RelativeLayout 容器时(该容器允许您根据其他 View 对象的位置来放置 View 对象),框架会执行以下操作:

  1. 执行一次“布局和度量”遍历。在此过程中,框架会根据每个子对象的请求计算该子对象的位置和大小。
  2. 结合此数据和对象的权重确定关联视图的恰当位置。
  3. 执行第二次布局遍历,以最终确定对象的位置。
  4. 进入渲染过程的下一个阶段。

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

此外,RelativeLayout 以外的容器也可能会导致 Double Taxation。例如:

  • LinearLayout 视图设置为水平方向,可能会导致执行双重“布局和度量”遍历。如果您添加 measureWithLargestChild,则垂直方向上也可能会发生双重“布局和度量”遍历,因为在这种情况下,框架可能需要执行第二次遍历才能正确解析对象的大小。
  • GridLayout 也有类似的问题。虽然该容器也允许相对定位,但它通常会通过预处理子视图之间的位置关系来避免 Double Taxation。不过,如果布局使用权重或使用 Gravity 类来填充,则会失去该预处理带来的好处,当容器为 RelativeLayout 时,框架可能必须执行多次遍历。

多次“布局和度量”遍历本身并不是性能负担,但如果发生在错误的地方,就可能会变成负担。您应该警惕容器存在以下情况:

  • 它是视图层次结构中的根元素。
  • 它下面有较深的视图层次结构。
  • 屏幕中填充了它的许多实例,类似于 ListView 对象中的子对象。

诊断视图层次结构问题

布局性能是一个涉及许多方面的复杂问题。有几种工具可以为您提供关于性能瓶颈发生位置的明确提示。其他一些工具提供的信息不那么明确,但也能提供有用的提示。

Systrace

有一个工具可以提供关于性能的重要数据,那就是 Systrace,该工具内置在 Android SDK 中。利用 Systrace 工具,您可以在整个 Android 设备上收集和检查计时信息,从而了解布局性能问题何时导致性能问题。要详细了解 Systrace,请参阅系统跟踪概览

GPU 渲染分析

另一款最有可能为您提供可靠的性能瓶颈信息的工具是设备端 GPU 渲染分析工具,该工具在搭载 Android 6.0(API 级别 23)及更高版本的设备上提供。通过此工具,您可以了解“布局和度量”阶段在每一帧渲染上花费的时间。此数据可以帮助您诊断运行时性能问题,以及确定需要解决哪些“布局和度量”问题(如果有)。

在其捕获的数据的图形表示中,GPU 渲染分析使用蓝颜色来代表布局时间。要详细了解如何使用此工具,请参阅 GPU 渲染分析演示

Lint

Android Studio 的 Lint 工具可以帮助您了解视图层次结构中的低效问题。要使用此工具,请依次选择 Analyze > Inspect Code,如图 1 中所示。

图 1. 在 Android Studio 中找到 Inspect Code

Android > Lint > Performance 下面显示了有关各种布局项目的信息。要查看更多详情,可以点击各个项目将其展开,然后在屏幕右侧的窗格中会显示详细信息。图 2 显示了此界面的一个示例。

图 2. 查看有关 Lint 工具发现的特定问题的信息。

点击其中一个项目,右侧窗格中就会显示与该项目相关的问题。

要详细了解该区域中显示的特定主题和问题,请参阅 Lint 文档。

Layout Inspector

Android Studio 的 Layout Inspector 工具提供关于应用视图层次结构的可视化表示。这是一种浏览应用层次结构的好方法,它提供关于特定视图父链的清晰的可视化表示,可帮助您检查应用构建的布局。

Layout Inspector 提供的视图还可以帮助您确定由“Double Taxation”引起的性能问题。它还可以让您通过一种简便的方法来识别嵌套布局的深层链或具有大量嵌套子级的布局区域,这是导致性能下降的另一个潜在因素。在这两种情况下,“布局和度量”阶段的开销可能会特别大,从而导致性能问题。

要了解详情,请参阅使用 Layout Inspector 调试布局

解决视图层次结构问题

要解决由视图层次结构引起的性能问题,其背后的基本原理很简单,但实际操作起来却比较困难。防止因视图层次结构导致性能下降包括两个目标:一个是实现视图层次结构扁平化,一个是减少“Double Taxation”。本节将探讨实现这两个目标的一些策略。

移除多余的嵌套布局

开发者经常会过度使用嵌套布局。例如,可能会在 RelativeLayout 容器包含一个同样也是 RelativeLayout 容器的子级。这种嵌套实际是多余的,并且会给视图层次结构造成不必要的开销。

Lint 通常可以为您标记此类问题,从而减少调试时间。

采用 merge/include

造成多余嵌套布局的一个常见原因就是 <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>
    

该 include 会将第一个布局嵌套在第二个布局中,而此嵌套是不必要的。

merge 标记可以避免此问题。要了解此标记,请参阅通过 <include> 重复使用布局

采用开销较低的布局

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

例如,您可能会发现,TableLayout 作为具有许多位置依赖项的更复杂的布局,可以提供相同的功能。在 Android 的 N 版本中,ConstraintLayout 类提供了与 RelativeLayout 类似的功能,但开销要低得多。