布局优化(上):过度绘制原理、Hierarchy Viewer工具使用、减少布局层级

布局优化这个话题,我做了这么多年Android,每次看新人写的布局文件,都忍不住想多说两句。说白了,布局是用户第一眼看到的东西,它卡不卡、流畅不流畅,直接影响用户对App的第一印象。今天咱们就聊聊过度绘制、层级优化这些事儿。

一、过度绘制:看不见的性能杀手

什么是过度绘制?简单说,就是一个像素点被重复画了好几次。你想想看,屏幕上的每个像素最终只显示一种颜色,但如果你在它上面叠了三层、四层的View,那GPU就得把这几个图层都画一遍,最后才合成出最终的颜色。这不就是白干活吗?

我在项目中遇到过这样一个场景:一个简单的列表页面,滑动起来帧率只有40多帧。当时我打开开发者选项里的「显示过度绘制」开关,好家伙,整个屏幕红得跟番茄似的。一层一层扒下去,发现是背景色、CardView的背景、还有Item里的图片背景,三层叠在一起了。

过度绘制的级别(Android开发者选项)

  • 蓝色:过度绘制1次(可接受)
  • 绿色:过度绘制2次(还行)
  • 粉色:过度绘制3次(需要优化)
  • 红色:过度绘制4次以上(必须优化)

怎么检查?很简单。打开手机的「开发者选项」→「调试GPU过度绘制」→「显示过度绘制区域」。然后跑你的App,看看哪些区域是红色、粉色的。我个人习惯是,保证主界面大部分区域是蓝色,最多允许少量绿色区域。

注意:过度绘制不等于卡顿,但它是卡顿的重要诱因。尤其是在低端机上,GPU性能本来就有限,过度绘制多了,掉帧是必然的。

二、Hierarchy Viewer:布局的「CT机」

Hierarchy Viewer这个工具,老Android工程师应该都不陌生。虽然现在Android Studio里有了更现代的Layout Inspector,但Hierarchy Viewer的很多理念依然值得学习。

这个工具能干什么?它能把你当前界面的View树给可视化出来。每个节点代表一个View,节点之间的连线代表父子关系。更重要的是,它还能告诉你每个View的测量、布局、绘制耗时。

我记得有一次,一个同事说他的页面加载特别慢,我打开Hierarchy Viewer一看,好家伙,View树深度达到了12层。一个简单的列表Item,外面套了三层LinearLayout,里面又套了两层RelativeLayout。这能不慢吗?

使用技巧

  • 连接真机时,需要设置环境变量 ANDROID_HVPROTO=ddm
  • 重点关注那些「红点」节点——它们代表绘制耗时较长
  • View树深度建议控制在5层以内,超过7层就要警惕了

现在Android Studio的Layout Inspector功能更强大,可以直接在模拟器或真机上抓取布局快照。我个人建议,每次写完一个页面,都用Layout Inspector看一眼View树,养成习惯。

三、减少布局层级:从RelativeLayout到ConstraintLayout

说到减少层级,就不得不提RelativeLayout和ConstraintLayout的对比。这个问题我面试的时候经常问,很多人答不上来。

先说说RelativeLayout。它有个特点:需要两次测量才能确定子View的位置。为什么?因为子View之间可能存在依赖关系,比如A在B的左边,B又在C的下边。第一次测量只能确定大概位置,第二次才能精确计算。这就意味着,RelativeLayout嵌套多了,测量成本会成倍增加。

而ConstraintLayout呢?它用一套约束系统,一次测量就能搞定所有子View的位置。而且ConstraintLayout的扁平化能力特别强——一个复杂的布局,用RelativeLayout可能要嵌套三四层,用ConstraintLayout一层就能搞定。

对比项 RelativeLayout ConstraintLayout
测量次数 至少2次 1次
嵌套层级 容易产生多层嵌套 扁平化,通常1-2层
性能表现 复杂布局下性能较差 整体更优
学习成本 中等

我曾经把一个用RelativeLayout嵌套了三层的页面,改成ConstraintLayout一层搞定。改造前,页面加载耗时约120ms;改造后,降到了45ms。这还只是一个页面,如果全App都这么改,效果可想而知。

来看一个实际例子。假设我们要实现这样一个布局:顶部一个图片,图片右下角有个小标签,图片下方是标题和描述,描述下方是两个按钮。

用RelativeLayout的写法大概是这样:

<RelativeLayout>
    <!-- 图片 -->
    <ImageView android:id="@+id/image"/>
    <!-- 标签,在图片右下角 -->
    <TextView 
        android:layout_alignRight="@id/image"
        android:layout_alignBottom="@id/image"/>
    <!-- 标题 -->
    <TextView 
        android:layout_below="@id/image"/>
    <!-- 描述 -->
    <TextView 
        android:layout_below="@id/title"/>
    <!-- 按钮容器 -->
    <LinearLayout 
        android:layout_below="@id/desc"
        android:orientation="horizontal">
        <Button/>
        <Button/>
    </LinearLayout>
</RelativeLayout>

看到了吗?这里已经出现了RelativeLayout嵌套LinearLayout的情况。如果需求再复杂一点,层级还会继续加深。

用ConstraintLayout改写:

<androidx.constraintlayout.widget.ConstraintLayout>
    <ImageView android:id="@+id/image"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"/>
    
    <TextView android:id="@+id/badge"
        app:layout_constraintEnd_toEndOf="@id/image"
        app:layout_constraintBottom_toBottomOf="@id/image"/>
    
    <TextView android:id="@+id/title"
        app:layout_constraintTop_toBottomOf="@id/image"
        app:layout_constraintStart_toStartOf="parent"/>
    
    <TextView android:id="@+id/desc"
        app:layout_constraintTop_toBottomOf="@id/title"
        app:layout_constraintStart_toStartOf="parent"/>
    
    <Button android:id="@+id/btn1"
        app:layout_constraintTop_toBottomOf="@id/desc"
        app:layout_constraintStart_toStartOf="parent"/>
    
    <Button android:id="@+id/btn2"
        app:layout_constraintTop_toBottomOf="@id/desc"
        app:layout_constraintStart_toEndOf="@id/btn1"/>
</ConstraintLayout>

全部在一层ConstraintLayout里搞定,没有嵌套。这就是ConstraintLayout的魅力。

核心原则:能用一层ConstraintLayout解决的问题,绝不用两层RelativeLayout。扁平化布局是性能优化的第一步。

四、知识体系总览

下面这张图,我把本章的核心知识点串起来了。你可以看到,过度绘制、层级优化、工具使用这三者其实是环环相扣的。

布局优化(上) 过度绘制原理 GPU重复绘制同一像素 开发者选项 → 显示过度绘制 蓝色可接受,红色需优化 Hierarchy Viewer 可视化View树结构 查看测量/布局/绘制耗时 Layout Inspector(新版) 减少布局层级 RelativeLayout vs ConstraintLayout ConstraintLayout一次测量 扁平化,深度控制在5层内 三者结合:检测 → 分析 → 优化,形成闭环

嗯,布局优化这个话题,说起来其实就三个字:少画、浅层、扁平。少画就是减少过度绘制,浅层就是控制View树深度,扁平就是用ConstraintLayout替代多层嵌套。这三板斧用好了,大部分布局性能问题都能解决。

个人建议

  • 写布局时,先想清楚结构,再动手写代码
  • 写完立刻用Layout Inspector看一眼View树
  • 如果发现红色过度绘制区域,优先检查背景色设置
  • 能用ConstraintLayout的地方,就别用RelativeLayout

我曾经在一个项目里,把全App的布局都过了一遍,光是去掉多余的背景色和扁平化布局,就让整体帧率提升了15%左右。你说这优化值不值?


公众号:蓝海资料掘金营,微信deep3321