android 过度绘制

·  阅读 2018

1.概述

  UI渲染操作通常依赖于两个核心组件:CPU与GPU。CPU负责包括Measure,Layout,Record,Execute的计算操作,GPU负责Rasterization(栅格化)操作。   所谓栅格化,就是将那些UI控件(如Button,Bitmap)拆分到不同的像素上进行显示。这是一个很费时的操作,GPU能够加快栅格化的操作。   为了能够使得App流畅,在Android中我们有一个16ms准则。这是因为Android平台的屏幕刷新频率一般为60Hz,也就是大概16.6ms一帧。也就是说在16ms内我们的CPU和GPU必须完成所有的计算,绘制,渲染操作,否则就有可能出现掉帧,卡顿现象,引发性能问题,伤害用户体验。   过度绘制(Overdraw)指的是屏幕上的某个像素在同一帧(16ms)的时间内被绘制了过多次数。   有很多原因可以导致丢帧,也许是因为你的layout太过复杂,无法在16ms内完成测量和布局,有可能是因为你的UI上有层叠太多的绘制单元,还有可能是因为动画执行的次数过多。也有可能是是主线程中做了其他耗时操作,或者线程数量太多。这些都会导致CPU或者GPU负载过重。

2.GPU过渡绘制调试

开启:adb shell setprop debug.hwui.overdraw show

关闭:adb shell setprop debug.hwui.overdraw false

3.过度绘制的分级:

原色:没有过度绘制

蓝色:1 次过度绘制

绿色:2 次过度绘制

粉色:3 次过度绘制(不应超过屏幕的四分之一)

红色:4 次及以上过度绘制(不能出现)

4.避免过度绘制

4.1去掉Activity的冗余的背景

(1)当我们新建一个Activity时,Activity的Theme主题上往往自动有一个背景。但是很多时候这个背景是多余的,我们自己使用的控件往往已经设置了背景;

在APP Theme中把背景改掉:

<style name="AppTheme" parent="android:Theme.Light.NoTitleBar">
    <item name="android:windowBackground">@null</item>
    ...
</style>
复制代码

(2)还有很多时候我们在Activity的布局文件中的根Layout上就已经设置了background属性,这样的情况下,这个Activity自带的背景就多余了。

在Activity的onCreate()中试用以下代码:

getWindow().setBackgroundDrawable(null);
复制代码

如果是在Fragment中,就是

getActivity().getWindow().setBackgroundDrawable(null)
复制代码

4.2 ImageView的background和setImageDrawable()重叠

  我们平时使用ImageView等类似的显示图片的控件时,通常会有一个加载未完成时的占位图,很多时候我们习惯用background属性进行添加。等到网络将图片请求到之后我们又使用setImageDrawable()设置了图片,这样子图片会覆盖掉原来的占位图,但是Android系统绘制界面时原来的占位图和现在的图片都会被绘制,造成了Overdraw。

就是占位图和后来加载到的图片都使用setImageDrawable()来设置,而不要使用background

4.3 减少Drawable的复杂Shape

  使用复杂的Shape有时候也是引起过度绘制的一个因素,比如Stroke,虽然只是描边,但是会增加整个区域的一层过度绘制。例如,将下面的配置作为ImageView的背景。

<solid android:color="#FFF8F8F8" />

<stroke
    android:dashGap="4dp"
    android:dashWidth="2dp"
    android:width="2dp"
    android:color="#ff1111" />

<padding
    android:bottom="10dp"
    android:left="10dp"
    android:right="10dp"
    android:top="10dp" />

<corners android:radius="6dp" />
复制代码
在有stroke属性的时候,显示为淡红色,去掉stroke属性后,显示为绿色。

4.4使视图层级扁平化

  现代布局使视图堆叠和分层更加容易。然而,这样做却会过度绘制,从而导致性能降低。特别在每一个堆叠视图对象是不透明的场景中,可见和不可见的像素都需要绘制在屏幕上。

  如果遇到这类问题,可以通过优化视图层次结构来减少重叠的UI对象数量,从而提升性能。

要想扁平化布局效果,可以使用约束性布局ConstraintLayout。

4.5自定义控件中onDraw函数的正确使用

  在自定义控件中,我们经常会通过onDraw函数绘制一些复杂的形状、文字、图片等。但是这些Canvas的绘制函数,每一次调用就会增加一层的绘制,次数越多也就会导致越多的过度绘制。如我们用如下代码来自定义绘制图形,就会存在绘制三次的情况:

protected void onDraw(Canvas canvas) {
    // TODO Auto-generated method stub
    int width = getWidth();
    int height = getHeight();
    Paint paint = new Paint();
    paint.setColor(0xFFFFFFFF);
    canvas.drawColor(0xFFFFFFFF);
    canvas.drawRect(20, 20, width - 20, height - 20, paint);
    canvas.drawCircle(width / 2, height / 2, 50, paint);
    
  }
复制代码

4.6通过Merge标签减少View树层次

  我们在自定义可重用布局的时候,都会新建一个文件来存放这个布局。当我们在文件中include这个布局文件的时候,很可能就额外多了一层的布局。如果这时候使用Merge的话,再使用包含这个的布局的时候,系统会自动忽略merge层级,把它的子view直接放置与include平级的布局下。还有一种情况,例如在LinearLayout等布局里面嵌入一个布局时,刚好这个布局的根节点也是LinearLayout等布局的时候,既两层布局使用了同一种类型的布局时,这样就多了一层没有用的嵌套,而这个时候如果使用merge根标签就可以避免这样的问题,减少这层无用的嵌套。

  不过只能作为XML布局的根标签使用。当Inflate以开头的布局文件时,必须指定一个父ViewGroup,并且必须设定attachToRoot为true。

4.7通过ViewStub标签优化布局

  一些显示错误的界面、加载提示框的界面,ProgressBar等界面,以及用户很少会触发的界面,当这些不是必须显示的布局都堆在一起时,会对infalte性能、内存等产生不利影响。这个时候用ViewStub标签就很合适,可以将这些不常用的布局另外保存为布局文件,对应的位置布局用ViewStub标签代替。等到真正需要显示的时候再对ViewStub初始化。因为ViewStub是一个轻量级的View,它是一个不可见,且不占布局位置,占用资源非常小的控件。所以使用ViewStub后,可以加快Inflate的时间,减少创建的对象数量,从而提升性能和减少内存的占用。不过ViewStub只能Inflate一次,一旦初始化后该标签就会被真正的布局所代替。

4.8减少不需要的View

  一方面减少层级本身就是减少了view的数量,另外像一些场景,文字有大有小,有不同颜色,其实用一个TextView就可以实现,例如可以借助Spannable对象实现。不需要使用很多个TextView来实现。

另外,有时我们发现一个空白区域的占位也使用了一个view去做,其实用layout_margin属性或者用其他方式就能代替掉这个View。

分类:
Android
标签:
分类:
Android
标签: