20、View的绘制优化:避免过度绘制、merge/include标签、ViewStub与硬件加速

各位同学,今天我们来聊聊View绘制的优化。说实话,这块内容我每次讲都觉得特别实在——因为它直接关系到App的流畅度。你想想看,用户打开你的App,滑动一下卡成PPT,那体验基本就凉了。

我在项目中遇到过不少性能问题,最后追根溯源,十有八九都跟绘制优化没做到位有关。今天我把几个最实用的优化手段掰开揉碎了讲给你听。

一、避免过度绘制(Overdraw)

过度绘制,说白了就是同一个像素点被重复画了好几次。比如你画了一个红色背景,又在上面盖了一层蓝色,再盖一层绿色——那GPU就得把红、蓝、绿都画一遍,最后只显示绿色。这不是白费力气吗?

Android里有个特别好用的工具叫「调试GPU过度绘制」。打开开发者选项,找到这个开关,屏幕上就会出现颜色指示:

颜色 含义 建议
无色 没有过度绘制 完美
蓝色 过度绘制1次 可接受
绿色 过度绘制2次 需要关注
粉色 过度绘制3次 必须优化
红色 过度绘制4次以上 严重问题

我个人习惯是,只要看到粉色或红色区域,就一定会去排查。常见的过度绘制场景有:

  • 背景重复设置:Activity设了背景,布局又设了背景,子View再设背景。其实只需要最外层设一次就够了。
  • 重叠的View:比如两个全屏的FrameLayout叠在一起,下面的那个完全被挡住,却还在绘制。
  • 不必要的透明区域:有些View明明不需要透明,却用了透明背景。

核心原则:能少画就少画,能不画就不画。每次绘制都是有代价的。

二、使用merge和include标签

这两个标签是布局复用的好帮手。我刚开始写Android时,经常把公共布局复制粘贴到各个页面,后来发现维护起来简直要命。改一个地方,得改七八个文件。

include标签:把公共布局抽出来,用include引入。比如每个页面都有的标题栏:

<!-- title_bar.xml -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="48dp"
    android:background="@color/primary">
    <TextView
        android:id="@+id/title_text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="标题" />
</LinearLayout>

<!-- 使用 -->
<include layout="@layout/title_bar" />

merge标签:这个更巧妙。它不会产生额外的View层级。比如你include了一个布局,如果外层是LinearLayout,include进来的布局又包了一层LinearLayout,那就多了一层没必要的嵌套。

用merge可以解决:

<!-- title_bar_merge.xml -->
<merge xmlns:android="http://schemas.android.com/apk/res/android">
    <TextView
        android:id="@+id/title_text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="标题" />
</merge>

这样include进来时,merge里的内容会直接展开到父布局里,不会多一层。嗯,这里要注意:merge只能作为根标签,而且父布局必须能容纳它的子View。

我的经验:每次写布局时,我都会问自己一句:「这个布局能不能用merge?能不能减少一层嵌套?」久而久之,你的布局层级会越来越浅,性能自然就上去了。

三、ViewStub的使用

ViewStub是个懒加载的利器。有些View不是一开始就需要显示的,比如「加载失败」的提示、用户协议弹窗等。如果用ViewStub包起来,它们一开始不会占用任何资源。

用法很简单:

<ViewStub
    android:id="@+id/error_stub"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout="@layout/layout_error" />

// 在代码中按需加载
ViewStub stub = findViewById(R.id.error_stub);
if (stub != null) {
    View errorView = stub.inflate();
    // 可以操作errorView里的子View了
}

为什么说它好?因为ViewStub本身非常轻量,它不参与测量和布局,也不绘制。只有当你调用inflate()时,它才会被替换成真正的布局。这比用View.GONE强多了——GONE虽然不显示,但View对象还在,还占内存。

注意:ViewStub一旦inflate之后,就不再是ViewStub了。你不能再次inflate它。所以适合那些「只显示一次」的场景。

四、硬件加速的优化技巧

Android从3.0开始引入了硬件加速,说白了就是用GPU来分担CPU的绘制工作。默认情况下,如果你的targetSdkVersion >= 14,硬件加速是开启的。

但硬件加速不是万能的。我曾经遇到过一个坑:某个页面在低端机上滑动特别卡,查了半天发现是硬件加速导致的。因为有些绘制操作在GPU上反而更慢,比如频繁的Canvas.save()和Canvas.restore()。

硬件加速的优化建议:

  • 减少过度绘制:硬件加速虽然快,但过度绘制依然会消耗GPU资源。
  • 避免频繁创建Bitmap:在onDraw里创建Bitmap,每次绘制都会触发GPU上传纹理,非常慢。
  • 使用硬件层(Hardware Layer):对于频繁重绘的View,可以开启硬件层缓存。
// 开启硬件层
view.setLayerType(View.LAYER_TYPE_HARDWARE, null);

// 动画结束后记得关闭
view.setLayerType(View.LAYER_TYPE_NONE, null);

硬件层相当于把View的绘制结果缓存成一张纹理,下次重绘时直接复用。但要注意:如果View的内容频繁变化,缓存反而会失效,得不偿失。

一句话总结:硬件加速是利器,但不是万能药。用得好,流畅如丝;用不好,卡成PPT。

知识体系总览

下面这张图是我整理的View绘制优化知识体系,你可以对照着查漏补缺:

View绘制优化知识体系 避免过度绘制 • 调试GPU过度绘制 • 减少背景重复 • 避免不必要的透明 • 裁剪不可见区域 布局层级优化 • merge标签 • include标签 • 减少嵌套层级 • ConstraintLayout ViewStub懒加载 • 按需加载布局 • 不占测量/布局 • 适合一次性场景 • 替代View.GONE 硬件加速优化 • GPU分担CPU • 硬件层缓存 • 避免频繁Bitmap • 注意低端机兼容 核心目标:减少绘制次数 + 降低绘制复杂度 + 按需加载 推荐工具:Profile GPU Rendering | Hierarchy Viewer | Systrace 优化不是一蹴而就,而是持续迭代的过程

好了,以上就是View绘制优化的核心内容。记住,优化的本质是「少做事」——少画、少嵌套、少加载。把这些原则刻在脑子里,你的App流畅度一定会提升一个档次。


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