zoukankan      html  css  js  c++  java
  • 【转】Android性能优化-过度绘制解决方案

    转载请注明出处:http://blog.csdn.net/a740169405/article/details/53896497

    过度绘制:

    屏幕上某一像素点在一帧中被重复绘制多次,就是过度绘制。 
    下图中多个卡片跌在一起,但是只有第一个卡片是完全可见的。背后的卡片只有部分可见。但是android系统在绘制时会将下层的卡片进行绘制,接着再将上层的卡片进行绘制。但其实,下层卡片不可见的部分是不需要进行绘制的,只有可见部分才需要进行绘制。 
    过度绘制demo

    依据过度绘制的层度可以分成: 
    - 无过度绘制(一个像素只被绘制了一次) 
    - 过度绘制x1(一个像素被绘制了两次) 
    - 过度绘制x2(一个像素被绘制了三次) 
    - 过度绘制x3(一个像素被绘制了四次) 
    - 过度绘制x4+(一个像素被绘制了五次以上)

    查看自己应用的过度绘制情况:

    方法一:通过开发者选项开启GPU过度绘制调试 
    Android手机的开发者选项中有『调试 GPU 过度绘制』的选项: 
    开发人员选项

    点开后后选择『显示过度绘制区域』: 
    过度绘制

    方法二:通过adb命令开启GPU过度绘制调试 
    当然,如果每次都进入系统设置嫌麻烦,可以使用adb命令进行开启和关闭: 
    开启『调试 GPU 过度绘制』:

    adb shell setprop debug.hwui.overdraw show
    • 1

    关闭『调试 GPU 过度绘制』:

    adb shell setprop debug.hwui.overdraw false
    • 1

    执行命令之后可能需要重新启动你当前开发的应用。

    颜色与过度绘制:

    • 原色:没有过度绘制
    • 蓝色:1 次过度绘制
    • 绿色:2 次过度绘制
    • 粉色:3 次过度绘制
    • 红色:4 次及以上过度绘制

    在平时的开发中,如果出现粉色及以上的过度绘制情况。说明过度绘制以及很严重了。需要进行优化。

    优化过度绘制:

    1. 去除Activity自带的默认背景颜色: 
    查看Android源码里的Theme主题,如下:

    <style name="Theme">
        ...
        <!-- Window attributes -->
        <item name="windowBackground">@drawable/screen_background_selector_dark</item>
        ...
    </style>
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    也就是说继承Theme这个style的风格,默认情况下,新建一个Activity都是有背景的。正常情况下,很多界面其实是不需要背景的。

    下面是华为自带天气APP的首页,我们可以看到文字部分以及图标部分都是绿色,说面已经是第三层过度绘制了,其中背后天气图是一层,文字又是一层,正常来说应该只有两层,也就是文字和图标应该是蓝色。那么这多出来的一层应该就是Activity自带的背景色了。也就是theme里面设置的。 
    Activity默认背景

    我们只要在自己的AppTheme里面去除该背景色即可:

    <style name="AppTheme" parent="android:Theme.Light.NoTitleBar">
        <item name="android:windowBackground">@null</item>
    </style>
    • 1
    • 2
    • 3

    或者在Activity的onCreate方法中:

    getWindow().setBackgroundDrawable(null);
    • 1

    2.使用Canvas的clipRect和clipPath方法限制View的绘制区域 
    一个Activity对应有一个Canvas,也就是画布,画布的概念就是一个画板,这个画布提供了很多的API,我们可以通过调用画布的API来绘图以及对画布做一些操作,clipRect方法用来裁切画布上的一个矩形区域,该矩形区域用Rect对象来描述。调用了clipRect之后,画布的可绘制区域减小到和Rect指定的矩形区域一样大小。所有的绘制将限制在该矩形范围之内。这里的裁切概念和PS里的裁切类似。

    典型的例子,抽屉布局,找了网易云音乐开刀: 
    网易云 
    注意观察左侧抽屉打开的时候,抽屉布局和背后布局重叠在一起了,此时整个屏幕一多半都变成了红色,过度绘制严重。

    在抽屉布局弹出时,抽屉布局是不透明的,也就是说抽屉布局背后挡住的内容布局是不需要绘制的,而网易云进行了绘制,导致抽屉布局所在区域的像素点绘制了多次。

    google官方在android.support.v4.widget包下有DrawerLayout.java类。使用来实现抽屉布局的。该类在重写了drawChild方法:

    @Override
    protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
        final int height = getHeight();
        // 判断是否是内容视图
        final boolean drawingContent = isContentView(child);
        int clipLeft = 0, clipRight = getWidth();
    
        // 记录当前画布信息
        final int restoreCount = canvas.save();
        if (drawingContent) {
            // 只有在绘制内容视图时才进行裁切
            final int childCount = getChildCount();
            for (int i = 0; i < childCount; i++) {
                final View v = getChildAt(i);
                if (v == child || v.getVisibility() != VISIBLE ||
                        !hasOpaqueBackground(v) || !isDrawerView(v) ||
                        v.getHeight() < height) {
                    // 如果child是内容视图/视图不可见/视图背景透明/不是抽屉视图/child高度小于父布局高度
                    // 则不做画布裁切
                    continue;
                }
    
                if (checkDrawerViewAbsoluteGravity(v, Gravity.LEFT)) {
                    // 盒子在左侧时裁切的left和right
                    final int vright = v.getRight();
                    if (vright > clipLeft) clipLeft = vright;
                } else {
                    // 盒子在右侧时裁切的的left和right
                    final int vleft = v.getLeft();
                    if (vleft < clipRight) clipRight = vleft;
                }
            }
            // 裁切画布
            canvas.clipRect(clipLeft, 0, clipRight, getHeight());
        }
        // 绘制子视图
        final boolean result = super.drawChild(canvas, child, drawingTime);
        // 回复到裁切之前的画布
        canvas.restoreToCount(restoreCount);
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40

    drawChild方法在ViewGroup类的dispatchDraw方法内被调用,用来绘制子视图,DrawerLayout类通过重写该方法,因为在所有孩子视图绘制之前都会调用drawChild方法,但是这里只需要对内容区域视图做裁切,当绘制内容区域视图时,取得抽屉视图的位置信息,如果抽屉视图可见、背景为不透明、抽屉高度和父布局高度一致时,取得抽屉视图左、上、右、下边缘在canvas中的位置信息。接着进行裁切,将内容视图未被挡住的部分区域裁切出来,并把裁切完的canvas交由子View进行绘制,这样,内容区域只有在裁切后的区域才会绘制,其他区域不进行绘制。待子View绘制完之后,恢复Canvas到裁切之前的状态,因为一个Window下的所有View都使用的是同一个Canvas,所以需要恢复状态给其他子View使用。

    下面看一个系统里的“下载”APP,使用的是DrawerLayout实现: 
    下载APP 
    应用中虽然内容区域是红色,但是抽屉视图拉出来之后,抽屉视图的过度绘制情况却比内容区域未被挡住的部分少。


    3. ImageView的background和imageDrawable重叠 
    Android中,所有的view均可以设置background。ImageView除了能够设置background之外,还能设置ImageDrawable。

    在开发中,很多时候需要显示图片,在图片加载出来之前通常是需要显示一张默认图片的,很多时候会使用ImageView的background属性来设置默认背景图,而imageDrawable来设置需要加载的图片。这样会导致一个问题,当图片加载到页面后,默认背景图被挡住了,但是却仍然然需要绘制,导致过度绘制情况的发生。

    解决方案是把背景图和真正加载的图片都通过imageDrawable方法进行设置。


    总结

    • Android中一个window对应一个Canvas,window下的所有视图(View/ViewGroup)使用的都是同一个canvas,视图树的父节点在调用子视图的View.draw之前,会对Canvas进行裁切,裁切的区域就是View在屏幕中所占的矩形区域,这也就是为什么超过View边界的内容会被裁切掉的原因。
    • 既然过度绘制值一个像素点被绘制多次,我们只要保证图片或者背景颜色不要叠加在一起即可。正确的方式应该是尽量减少带背景的View产生重叠区域。如果重叠,使用canvas的clipRect进行裁切。
    • 尽量减少视图的深度,来减少视图树的遍历过程。
  • 相关阅读:
    Codeforces 1255B Fridge Lockers
    Codeforces 1255A Changing Volume
    Codeforces 1255A Changing Volume
    leetcode 112. 路径总和
    leetcode 129. 求根到叶子节点数字之和
    leetcode 404. 左叶子之和
    leetcode 104. 二叉树的最大深度
    leetcode 235. 二叉搜索树的最近公共祖先
    450. Delete Node in a BST
    树的c++实现--建立一棵树
  • 原文地址:https://www.cnblogs.com/qianyukun/p/8533674.html
Copyright © 2011-2022 走看看