实战 Android中的UI过度绘制

作者: 宝塔山上的猫 | 来源:发表于2016-04-19 14:30 被阅读9309次

    相信很多人都有这种经历,在使用app的过程中,突然间发现程序虽然在运行,但是这里停顿一下,那里停顿一下的卡顿现象,就像看上网看视频一样,缓冲不过来,视频很卡,不能连续的看下去。造成这样原因有很多,其中一种就是UI被过度绘制了。
    UI过度绘制简单的来说是指在一个界面中有很多元素,但是我们只需要更新某一小块的元素,app却把所有的元素都刷新一遍,这就造成过度绘制。

    overdraw_hidden_view.png

    过度绘制造成UI卡顿的原因是因为它浪费大量的CPU以及GPU资源。手机原本为了保持视觉的流畅度,其屏幕刷新频率是60hz,即在1000/60=16.67ms内更新一帧。如果没有完成任务,就会发生掉帧的现象,也就是我们所说的卡顿。
    这其中的原理比较复杂,大家可以看看大神是怎么说的,这里给个胡凯大神的文章地址:

    Android性能优化之渲染篇
    http://hukai.me/android-performance-render/

    debug GPU overdraw

    有问题就必然有解决办法,在Android系统内部也有一个神器可以查看app的UI的过度绘制情况,在开发者选项中有个debug GPU overdraw(调试GPU过度绘制),打开之后有off(关闭),show overdraw areas(显示过度绘制区域),show areas for Deuteranomaly(为红绿症患者显示过度绘制区域)


    overdraw.png

    我们选择show overdraw areas,发现整个手机界面的颜色变了,在打开过度绘制选项后,其中的蓝色,淡绿,淡红,深红代表了4种不同程度的Overdraw情况,我们的目标就是尽量减少红色Overdraw,看到更多的蓝色区域。


    color.png

    Profile GPU rendering

    其次android系统还内置了Profile GPU rendering工具,这个工具也是在开发者选项中打开,它能够以柱状图的方式显示当前界面的渲染时间


    Profile GPU rendering.png
    • 蓝色代表测量绘制的时间,或者说它代表需要多长时间去创建和更新你的DisplayList.在Android中,一个视图在可以实际的进行渲染之前,它必须被转换成GPU所熟悉的格式,简单来说就是几条绘图命令,复杂点的可能是你的自定义的View嵌入了自定义的Path. 一旦完成,结果会作为一个DisplayList对象被系统送入缓存,蓝色就是记录了需要花费多长时间在屏幕上更新视图(说白了就是执行每一个View的onDraw方法,创建或者更新每一个View的Display List对象).
    • 橙色部分表示的是处理时间,或者说是CPU告诉GPU渲染一帧的地方,这是一个阻塞调用,因为CPU会一直等待GPU发出接到命令的回复,如果柱状图很高,那就意味着你给GPU太多的工作,太多的负责视图需要OpenGL命令去绘制和处理.
    • 红色代表执行的时间,这部分是Android进行2D渲染 Display List的时间,为了绘制到屏幕上,Android需要使用OpenGl ES的API接口来绘制Display List.这些API有效地将数据发送到GPU,最总在屏幕上显示出来.

    在这里也放一个大神关于Profile GPU rendering的介绍

    http://androidperformance.com/2015/04/19/Android-Performance-Patterns-4.html

    下面我们开始对UI多度绘制开始实战吧!
    实战项目地址

    安卓UI问题
    https://github.com/lzyzsd/AndroidUIPorblems

    初始界面的问题

    刚打开这个项目,我们就发现了在第一个有过度绘制问题,效果如下


    main界面过度绘制.PNG

    存在问题

    • 在按钮overdraw上面就有个红色的过度绘制区域
    • 在文本框This is test的布局中也是红色过度绘制区域

    解决方法

    • 要解决这个问题,我们首先需要分析这是怎么引起的。分析到activity_main.xml的布局文件时,发现这里使用了多个嵌套的LinearLayout布局,而且每个LinearLayout都会使用一次android:background设置一次自己的背景颜色,他们造成了过度绘制。
      仔细分析在其中一个嵌套ImageView的LinearLayout布局背景颜色与最外层的背景颜色是一样的,属于不需要的背景色,因此将这个LinearLayout中的android:background属性删除,这时发现文本框布局已经不再是红色了


      第一次优化.png
    • 咋看之下一切都很完美,但其实整个ui其实还含有一个隐含的绘制效果,那边是在activity中,使用setContentView(R.layout.activity_main)设置布局的时候,android会自动填充一个默认的背景,而在这个UI中,我们使用了填充整个app的背景,因此不需要默认背景,取消也很简单,只需要在activity中的onCreate方法中添加这么一句就行了

    getWindow().setBackgroundDrawable(null);

    现在看最终优化效果


    最后结果.png

    OVERDRAWVIEW页面的问题

    在overdrawviewactivity中只有一个自定义的图案,而这个自定义的图案引起了过度绘制的问题


    OVERDRAWVIEW过度绘制.PNG

    解决方法

    • 首先这个也是填充了整个ui界面的绘制图片,因此我们也在activity中的onCreate方法中添加getWindow().setBackgroundDrawable(null);取消默认绘制。
    • 继续研究,发现过度绘制问题是由于OverDrawView类中的ondraw方法中多次绘制了矩形导致的,代码如下:

    @Override
    protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    int width = getWidth();
    int height = getHeight();
    mPaint.setColor(Color.GRAY);
    canvas.drawRect(0, 0, width, height, mPaint);
    mPaint.setColor(Color.CYAN);
    canvas.drawRect(0, height/4, width, height, mPaint);
    mPaint.setColor(Color.DKGRAY);
    canvas.drawRect(0, height/3, width, height, mPaint);
    mPaint.setColor(Color.LTGRAY);
    canvas.drawRect(0, height/2, width, height, mPaint);
    }

    通过分析得知,颜色为GRAY的矩形的高度其实不需要设置为整个屏幕的高度,它的高度只需要设置为它所显示范围的高度就可以了,因此可以设为height/4。
    其他的矩形也是同样的道理,因此更改这里的代码为:

    @Override
    protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);

         int width = getWidth();
    
          int height = getHeight();
    
        mPaint.setColor(Color.GRAY);
    
          canvas.drawRect(0, 0, width, height/4, mPaint);
    
        mPaint.setColor(Color.CYAN);
    
          canvas.drawRect(0, height/4, width, height/3, mPaint);
    
         mPaint.setColor(Color.DKGRAY);
    
          canvas.drawRect(0, height/3, width, height/2, mPaint);
          mPaint.setColor(Color.LTGRAY);
    
        canvas.drawRect(0, height/2, width, height, mPaint);
    
    }
    

    优化的界面


    最终优化.png

    BUSYONDRAW频繁绘制

    当我们点击BUSYONDRAW按钮的时候,我们发现明显的卡顿现象。在开发者选项中打开Profile GPU rendering选项,然后在次点击BUSYONDRAW按钮,发现这个页面绘制渲染时间已经突破天际了!


    绘制时间1.png

    在初始的时候,蓝色绘制时间占满整个屏幕高度,这是造成卡顿的重要原因。

    解决方法

    • 首先卡顿现象是由ondraw方法中的for循环中的打印字符串引起的

    for (int i = 0; i < 1000; i++) {
    System.out.println("canvas = [" + canvas + "]" + i);
    }

    我们将其提取出来放在另一个线程中运行。
    先是创建一个线程池:

    private ExecutorService pool = Executors.newCachedThreadPool();

    将要运行的耗时操作封装成Runable对象并通过一个方法获取:

    @NonNull
    private Runnable getCommand(final Canvas canvas) {
    return new Runnable() {
    @Override
    public void run() {
    for (int i = 0; i &amp;lt; 1000; i++) {
    System.out.println("canvas = [" + canvas + "]" + i);
    }
    }
    };
    }

    最后在onDraw里运行放在子线程里运行:

    pool.execute(getCommand(canvas));
    现在进入也不会卡顿了。

    • 其次在ondraw中也不宜创建Paint()对象,因为app会频繁调用ondraw对象,会造成内存泄漏,因此需要将其提取为全局变量。

    • 最后绘制了多个圆形图案也造成了一定程度的卡顿,但由于功力不够,暂时无法优化。

    相关文章

      网友评论

        本文标题:实战 Android中的UI过度绘制

        本文链接:https://www.haomeiwen.com/subject/rjaalttx.html