你最需要知道的View优化

我请教下,你最需要知道的View优化
最新回答
逗二比

2026-10-30 20:27:06

View优化之Overdraw

一、什么是Overdraw?

Overdraw即过度绘制,是指在一帧的时间内(16.67ms)像素被绘制了多次。理论上,一个像素每次只绘制一次是最优的,但由于重叠的布局,一些像素会被多次绘制。每次绘制都会对应到CPU的一组绘图命令和GPU的一些操作,当这些操作耗时超过16.67ms时,就会出现掉帧现象,即卡顿。因此,对重叠不可见元素的重复绘制会产生额外的开销,需要尽量减少Overdraw的发生。

二、如何检测Overdraw?

Android提供了测量Overdraw的选项,在开发者选项-调试GPU过度绘制(Show GPU Overdraw)中,打开该选项就可以看到当前页面Overdraw的状态。该工具会使用三种不同的颜色绘制屏幕,来指示overdraw发生在哪里以及程度如何:

  • 没有颜色:意味着没有overdraw,像素只画了一次。
  • 蓝色:意味着overdraw 1倍,像素绘制了两次。大片的蓝色还是可以接受的(若整个窗口是蓝色的,可以摆脱一层)。
  • 绿色:意味着overdraw 2倍,像素绘制了三次。中等大小的绿色区域是可以接受的,但应该尝试优化、减少它们。
  • 浅红:意味着overdraw 3倍,像素绘制了四次,小范围可以接受。
  • 暗红:意味着overdraw 4倍,像素绘制了五次或者更多。这是错误的,需要修复。

三、如何减少Overdraw?

  1. 合理选择控件容器

    LinearLayout:易用,效率高,但表达能力有限,只能用于描述一个方向上连续排列的控件。

    RelativeLayout:复杂,表达能力强,效率稍逊,但可以用于描述任意复杂度的界面。

    在选择容器时,要根据实际情况来选择合适容器控件,在保证性能的同时,尽量避免overdraw。如果某一界面在使用LinearLayout并不会比RelativeLayout带来更多的控件数和控件层级时,LinearLayout则是首选。

  2. 去掉window的默认背景

    当使用了Android自带的一些主题时,window会被默认添加一个纯色的背景。如果自定义布局时又添加了一张背景图或设置背景色,那么DecorView的background就会产生一次Overdraw。可以通过在onCreate()中setContentView()之后调用getWindow().setBackgroundDrawable(null);或在theme中添加android:windowbackground="null"来去掉window的背景。

  3. 去掉其他不必要的背景

    有时候为了方便,会先给Layout设置一个整体的背景,再给子View设置背景,这会造成重叠。如果子View宽度为match_parent,完全覆盖了Layout的一部分,就可以通过分别设置背景来减少重绘。另外,如果采用的是selector的背景,将normal状态的color设置为“@android:color/transparent”,也可以解决问题。

  4. ClipRect & QuickReject

    为了解决Overdraw的问题,Android系统会通过避免绘制那些完全不可见的组件来尽量减少消耗。但对于复杂的自定义View(通常重写了onDraw方法),Android系统无法检测在onDraw里面具体会执行什么操作,因此无法避免Overdraw。可以通过canvas.clipRect()来帮助系统识别那些可见的区域,只有在这个区域内才会被绘制,其他区域会被忽视。同时,canvas.quickreject()也可以用来判断是否没和某个矩形相交,从而跳过那些非矩形区域内的绘制操作。

  5. ViewStub

    ViewStub是一个轻量级的View,它看不见、不占布局位置、占用资源非常小。可以为ViewStub指定一个布局,在Inflate布局的时候,只有ViewStub会被初始化。当ViewStub被设置为可见时,或调用了ViewStub.inflate()时,ViewStub所指向的布局就会被Inflate和实例化。这样可以使用ViewStub来方便地在运行时决定是否显示某个布局。

  6. Merge

    Merge标签可以干掉一个view层级。有两种情况下可以使用Merge标签来做容器控件:一是子视图不需要指定任何针对父视图的布局属性;二是假如需要在LinearLayout里面嵌入一个布局(或视图),而这个布局(或视图)的根节点也是LinearLayout,这样就多了一层没有用的嵌套。使用Merge标签可以避免这样的问题。但需要注意的是,Merge只能作为XML布局的根标签使用。

  7. 善用draw9patch

    给ImageView加一个边框时,通常会在ImageView后面设置一张背景图来露出边框。但这样做会导致两层drawable的重叠区域绘制两次,产生overdraw。优化方案是将背景drawable制作成draw9patch,并将和前景重叠的部分设置为透明。由于Android的2D渲染器会优化draw9patch中的透明区域,从而优化了这次overdraw。但需要注意的是,背景图片必须制作成draw9patch才行。

  8. 慎用Alpha

    对View做Alpha转化需要先将View绘制出来,然后做Alpha转化,最后将转换后的效果绘制在界面上。这样做会导致对当前View绘制两遍,绘制效率会大打折扣。如果一定需要做Alpha转化,可以采用缓存的方式,通过setLayerType方式将当前界面缓存在GPU中,但用完要马上释放掉。

  9. 避免“OverDesign”

    Overdraw产生的原因无外乎复杂的Layout层级、重叠的View、重叠的背景等。开发人员无节制的View堆砌,究其根本是产品无节制的需求设计。因此,在复杂的业务逻辑与简单易用的界面展现中需要做一个平衡,而不是一味的OverDesign。

通过以上方法,可以有效地减少Overdraw,提高应用的性能。