Android性能优化--过度绘制

渲染机制

前提知识

android 的渲染主要分为两个组件:1.CPU 2.GPU,由这两者共同完成在屏幕上绘制 。

  • CPU:中央处理器,它集成了运算,缓冲,控制等单元,包括绘图功能.CPU将对象处理为多维图形,纹理(Bitmaps、Drawables等都是一起打包到统一的纹理)。

  • GPU:一个类似于CPU的专门用来处理Graphics的处理器,用来帮助加快格栅化操作,当然,也有相应的缓存数据(例如缓存已经光栅化过的bitmap等)机制。

  • OpenGL ES:手持嵌入式设备的3DAPI,跨平台的、功能完善的2D和3D图形应用程序接口API,有一套固定渲染管线流程。

  • DisplayList:把XML布局文件转换成GPU能够识别并绘制的对象。这个操作是在DisplayList的帮助下完成的。DisplayList持有所有将要交给GPU绘制到屏幕上的数据信息。

  • 格栅化:将图片等矢量资源,转化为一格格像素点的像素图,显示到屏幕上。

  • 垂直同步VSYNC:让显卡的运算和显示器刷新率一致以稳定输出的画面质量。它告知GPU在载入新帧之前,要等待屏幕绘制完成前一帧。下面的三张图分别是GPU和硬件同步所发生的情况,Refresh
    Rate:屏幕一秒内刷新屏幕的次数,由硬件决定,例如60Hz.而Frame Rate:GPU一秒绘制操作的帧数,单位fps。如下图:

Android性能优化--过度绘制_第1张图片

渲染流程

UI对象—->CPU处理为多维图形,纹理 —–通过OpeGL ES接口调用GPU—-> GPU对图进行光栅化(Frame Rate ) —->硬件时钟(Refresh Rate)—-垂直同步—->投射到屏幕

渲染情况

  • 正常情况

Android系统每隔16ms发出VSYNC信号(1000ms/60=16.66ms),触发对UI进行渲染, 如果每次渲染都成功,这样就能够达到流畅的画面所需要的60fps,为了能够实现60fps,这意味着计算渲染的大多数操作都必须在16ms内完成。正常情况如下:
Android性能优化--过度绘制_第2张图片
注意:使顺利在60帧,每帧必须小于16毫秒完成。

  • 渲染超时

当这一帧画面渲染时间超过16ms的时候,垂直同步机制会让显示器硬件 等待GPU完成栅格化渲染操作, 这样会让这一帧画面,多停留了16ms,甚至更多.这样就这造成了 用户看起来 画面停顿.。
Android性能优化--过度绘制_第3张图片
当GPU渲染速度过慢,就会导致,某些帧显示的画面内容就会与上一帧的画面相同。

通过上面的介绍,我们知道渲染中可能出现的问题,那么如何避免或者说尽量减少这种问题的发生呢?接下来就是本文要说的过度绘制,减少不必要的绘制,尽量会减少渲染的时间。

过度绘制

过度绘制(Overdraw)描述的是屏幕上的某个像素在同一帧的时间内被绘制了多次。在多层次的UI结构里面, 如果不可见的UI也在做绘制的操作,这就会导致某些像素区域被绘制了多次。这就浪费大量的CPU以及GPU资源。

检测工具

按照以下步骤打开Show GPU Overrdraw的选项:设置 -> 开发者选项 -> 调试GPU过度绘制 -> 显示GPU过度绘制。
Android性能优化--过度绘制_第4张图片

  • 蓝色: 代表1层覆盖。像素绘制了两次。大片的蓝色还是可以接受的,若整个窗口是蓝色的,可以摆脱一层。
  • 绿色: 代表2层覆盖。像素绘制了三次。中等大小的绿色区域是可以接受的但你应该尝试优化、减少它们。
  • 淡红: 代表3层覆盖倍。像素绘制了四次,小范围可以接受。
  • 深红: 代表4层覆盖。像素绘制了五次或者更多。这是错误的,要修复它们。

渲染耗时呈现工具

打开“开发者选项”->“GPU呈现模式分析”->“在屏幕上显示为条形图”
Android性能优化--过度绘制_第5张图片
这个会分别显示关于StatusBar,NavBar,激活的程序Activity区域的GPU Rendering信息。Activity区域有一根绿色的横线,代表16ms,我们需要确保每一帧花费的总时间都低于这条横线,这样才能够避免出现卡顿的问题。

界面上会滚动显示垂直的柱状图来表示每帧画面所需要渲染的时间,柱状图越高表示花费的渲染时间越长。每一条柱状线都包含三部分:

  • 红色代表OpenGL渲染Display
    List所需要的时间,假如当前界面的视图越多,那么红色便会“跳”得越高。但其实这可能并不意味着你卡住了。
  • 黄色通常较短,它代表着CPU通知GPU“你已经完成视图渲染了”,这里CPU会等待GPU的回话。假如黄色部分很高的话,说明当前GPU过于忙碌,有很多命令需要去处理。
  • 蓝色代表了视图绘制所花费的时间,表示视图在界面发生变化(更新)的用时情况。蓝色对于判断流畅度的参考意义是较大。当它越长时,说明当前视图较复杂或者无效需要重绘,即我们通常说的“卡了”。

优化策略

1.减少布局层级

关闭相关手机上的开发者检测工具开关,打开Android Device Monitor, 找到 Hierarychy view 查看自己的布局找到,深的层级,是否可以做优化. 最外层父容器 是否需要。

使用线性布局LinearLayout排版导致UI层次变深,如果有这类问题,我们就使用相对布局RelativeLayout代替LinearLayout,减少UI的层次;如果不能减少布局层次推荐使用LinearLayout。

没有用的父布局时指没有背景绘制或者没有大小限制的父布局,这样的布局不会对UI效果产生任何影响。我们可以把没有用的父布局,通过merge标签合并来减少UI的层次;

2.去除不必要的背景

有时候为了方便会先给Layout设置一个整体的背景,再给子View设置背景,这里也会造成重叠,如果子View宽度mach_parent,可以看到完全覆盖了Layout的一部分,这里就可以通过分别设置背景来减少重绘。再比如如果采用的是selector的背景,将normal状态的color设置为“@android:color/transparent”,也同样可以解决问题。适时使用Color.TRANSPARENT,因为透明色Color.TRANSPARENT是不会被渲染的,他是透明的。

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

注意:在不同安卓版本的测试机中,去掉相同层次的背景色后,有的显示正常,有的不正常。

3.优化自定义View的计算

学会裁剪掉View的覆盖部分,增加cpu的计算量,来优化GPU的渲染,这个API可以很好的帮助那些有多组重叠 组件的自定义View来控制显示的区域。同时clipRect方法还可以帮助节约GPU资源,在clipRect区域之外的绘制指令都不会被执行,那些部分内容在矩形区域内的组件,仍然会得到绘制。并且在onDraw方法中减少View的重复绘制。

下面一个运用clipRect的例子

public class CardView extends View {

    private Bitmap[] mCards = new Bitmap[3];

    private int[] mImgId = new int[]{R.mipmap.a, R.mipmap.b, R.mipmap.c};

    public CardView(Context context) {
        super(context);
        for (int i = 0; i < mCards.length; i++) {
            Bitmap bm = BitmapFactory.decodeResource(getResources(), mImgId[i]);
            mCards[i] = Bitmap.createScaledBitmap(bm, 510, 510, false);
        }
    setBackgroundColor(Color.WHITE);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.save();
        canvas.translate(50, 200);
        for (Bitmap bitmap : mCards) {
            canvas.translate(150, 0);
            canvas.drawBitmap(bitmap, 0, 0, null);
        }
        canvas.restore();
    }
}

对于这段代码,没有使用clipRect方法前,过度绘制检测效果图:
Android性能优化--过度绘制_第6张图片

修改onDraw方法:

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    canvas.save();
    canvas.translate(50, 200);
    for (int i = 0; i < mCards.length; i++) {
        canvas.translate(150, 0);
        canvas.save();
        if (i < mCards.length - 1) {
            canvas.clipRect(0, 0, 150, mCards[i].getHeight());
        }
        canvas.drawBitmap(mCards[i], 0, 0, null);
        canvas.restore();
    }
    canvas.restore();
}

去除windowbackground :

getWindow().setBackgroundDrawable(null);
//getWindow().setBackgroundDrawableResource(android.R.color.transparent);

再看下过度绘制检测效果图:
Android性能优化--过度绘制_第7张图片

平时的开发过程中,需要权衡效果和性能,根据需求进行选择. ,养成良好的思考习惯。

你可能感兴趣的:(android)