0
点赞
收藏
分享

微信扫一扫

Android之如何避免Overdraw

回溯 2022-09-14 阅读 89


Android性能优化的一种吧,大体六种方法:

  • 1.去掉window的默认背景
  • 2.去掉其他不必要的背景
  • 3.ClipRect & QuickReject
  • 4.ViewStub
  • 5.Merge
  • 6.慎用Alpha
  • 7.善用draw9patch

overdraw 的关键点在于一个像素被绘制了几次,即使是 10 层布局,只要没有背景图,同样不会造成 overdraw

1. 概念

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

2. 如何测量 Overdraw

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

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

Android之如何避免Overdraw_android

3.合理选择控件容器

就是 布局优化,尽量减少布局的层级

布局中即可使用 LinearLayout 又可 使用 RelativeLayout 就使用 LinearLayout。因为 RL 功能比较复杂,布局过程需要花更多的 CPU 时间。FrameLayout 和 LinearLayout 都是都是简单高效的 ViewGroup ,可以考虑使用它们。但如果较为复杂,还是使用 RL ,比较牛逼。

有点跑题?算了,不想删,写着不容易

4. 去掉window的默认背景

当我们使用了Android自带的一些主题时,window会被默认添加一个纯色的背景,这个背景是被DecorView持有的。当我们的自定义布局时又添加了一张背景图或者设置背景色,那么DecorView的background此时对我们来说是无用的,但是它会产生一次Overdraw,带来绘制性能损耗。

去掉window的背景可以在 ​​onCreate()​​​中​​setContentView()​​之后调用

getWindow().setBackgroundDrawable(null);

或者在theme中添加

android:windowbackground="null";

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

有时候为了方便会先给Layout设置一个整体的背景,再给子View设置背景,这里也会造成重叠,如果子View宽度mach_parent,可以看到完全覆盖了Layout的一部分,这里就可以通过分别设置背景来减少重绘。

再比如如果采用的是selector的背景,将normal状态的color设置为“​​@android:color/transparent"​​,也同样可以解决问题。

开发过程中我们为某个View或者ViewGroup设置背景的时候,先思考下是否真的有必要,或者思考下这个背景能不能分段设置在子View上,而不是图方便直接设置在根View上

6. ClipRect & QuickReject

为了解决Overdraw的问题,Android系统会通过避免绘制那些完全不可见的组件来尽量减少消耗。

但是不幸的是,对于那些过于复杂的自定义的View(通常重写了onDraw方法),Android系统无法检测在onDraw里面具体会执行什么操作,系统无法监控并自动优化,也就无法避免Overdraw了。

但是我们可以通过​​canvas.clipRect()​​来帮助系统识别那些可见的区域。这个方法可以指定一块矩形区域,只有在这个区域内才会被绘制,其他的区域会被忽视

这个API可以很好的帮助那些有多组重叠组件的自定义View来控制显示的区域。同时clipRect方法还可以帮助节约CPU与GPU资源,在clipRect区域之外的绘制指令都不会被执行,那些部分内容在矩形区域内的组件,仍然会得到绘制。除了clipRect方法之外,我们还可以使用​​canvas.quickreject()​​来判断是否没和某个矩形相交,从而跳过那些非矩形区域内的绘制操作

7. ViewStub 高效占位符

我们经常会遇到这样的情况,运行时动态根据条件来决定显示哪个View或布局。常用的做法是把View都写在上面,先把它们的可见性都设为View.GONE,然后在代码中动态的更改它的可见性。这样的做法的优点是逻辑简单而且控制起来比较灵活。但是它的缺点就是,耗费资源

虽然把View的初始可见View.GONE但是在​Inflate​布局的时候​View​仍然会被​Inflate​,也就是说仍然会创建对象,会被实例化,会被设置属性。也就是说,会耗费内存等资源

推荐的做法是使用android.view.ViewStub,ViewStub是一个轻量级的View(宽高都为0),它一个看不见的,不占布局位置,占用资源非常小的控件。可以为ViewStub指定一个布局,在Inflate布局的时候,只有ViewStub会被初始化,然后当ViewStub被设置为可见的时候,或是调用了ViewStub.inflate()的时候,ViewStub所向的布局就会被Inflate和实例化,然后ViewStub的布局属性都会传给它所指向的布局。这样,就可以使用ViewStub来方便的在运行时,要还是不要显示某个布局

<ViewStub
android:id="@+id/stub_view"
android:inflatedId="@+id/panel_stub"
android:layout="@layout/progress_overlay"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"

当你想加载布局时,可以使用下面其中一种方法:

((ViewStub) findViewById(R.id.stub_view)).setVisibility(View.VISIBLE);
View importPanel = ((ViewStub) findViewById(R.id.stub_view)).inflate();

8. 慎用Alpha

假如对一个View做Alpha转化,需要先将View绘制出来,然后做Alpha转化,最后将转换后的效果绘制在界面上。通俗点说,做Alpha转化就需要对当前View绘制两遍,可想而知,绘制效率会大打折扣,耗时会翻倍,所以Alpha还是慎用

如果一定做Alpha转化的话,可以采用缓存的方式

view.setLayerType(LAYER_TYPE_HARDWARE);
doSmoeThing();
view.setLayerType(LAYER_TYPE_NONE);

通过setLayerType方式可以将当前界面缓存在GPU中,这样不需要每次绘制原始界面,但是GPU内存是相当宝贵的,所以用完要马上释放掉

9. 善用draw9patch

给ImageView加一个边框,你肯定遇到过这种需求,通常在ImageView后面设置一张背景图,露出边框便完美解决问题,此时这个ImageView,设置了两层drawable,底下一层仅仅是为了作为图片的边框而已。但是两层drawable的重叠区域去绘制了两次,导致overdraw

优化方案: 将背景drawable制作成draw9patch,并且将和前景重叠的部分设置为透明。由于Android的2D渲染器会优化draw9patch中的透明区域,从而优化了这次overdraw。 但是背景图片必须制作成draw9patch才行,因为Android 2D渲染器只对draw9patch有这个优化,否则,一张普通的Png,就算你把中间的部分设置成透明,也不会减少这次overdraw


举报

相关推荐

0 条评论