临文乍了了,彻卷兀若无。这篇文章主要讲述Android性能优化-过度绘制解决方案相关的知识,希望能为你提供帮助。
转载请注明出处:http://blog.csdn.net/a740169405/article/details/53896497过度绘制:屏幕上某一像素点在一帧中被重复绘制多次,就是过度绘制。
下图中多个卡片跌在一起,但是只有第一个卡片是完全可见的。背后的卡片只有部分可见。但是android系统在绘制时会将下层的卡片进行绘制,接着再将上层的卡片进行绘制。但其实,下层卡片不可见的部分是不需要进行绘制的,只有可见部分才需要进行绘制。
文章图片
依据过度绘制的层度可以分成:
- 无过度绘制(一个像素只被绘制了一次)
- 过度绘制x1(一个像素被绘制了两次)
- 过度绘制x2(一个像素被绘制了三次)
- 过度绘制x3(一个像素被绘制了四次)
- 过度绘制x4+(一个像素被绘制了五次以上)
查看自己应用的过度绘制情况:方法一:通过开发者选项开启GPU过度绘制调试
Android手机的开发者选项中有『调试 GPU 过度绘制』的选项:
文章图片
点开后后选择『显示过度绘制区域』:
文章图片
方法二:通过adb命令开启GPU过度绘制调试
当然,如果每次都进入系统设置嫌麻烦,可以使用adb命令进行开启和关闭:
开启『调试 GPU 过度绘制』:
adb shell setprop debug.hwui.overdraw show
- 1
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
下面是华为自带天气APP的首页,我们可以看到文字部分以及图标部分都是绿色,说面已经是第三层过度绘制了,其中背后天气图是一层,文字又是一层,正常来说应该只有两层,也就是文字和图标应该是蓝色。那么这多出来的一层应该就是Activity自带的背景色了。也就是theme里面设置的。
文章图片
我们只要在自己的AppTheme里面去除该背景色即可:
<
style name="AppTheme" parent="android:Theme.Light.NoTitleBar">
<
item name="android:windowBackground">
@null<
/item>
<
/style>
- 1
- 2
- 3
getWindow().setBackgroundDrawable(null);
- 1
一个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
下面看一个系统里的“下载”APP,使用的是DrawerLayout实现:
文章图片
应用中虽然内容区域是红色,但是抽屉视图拉出来之后,抽屉视图的过度绘制情况却比内容区域未被挡住的部分少。
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进行裁切。
- 尽量减少视图的深度,来减少视图树的遍历过程。
推荐阅读
- APP产品开发需注意这些方面
- Android开发发布真机调试
- Invalid bound statement (not found): com.ros.dao.LogMapper.insert
- Android启动页面实现版本检查更新
- Android查缺补漏(线程篇)-- AsyncTask的使用及原理详细分析
- 跨平台 webapp 开发技术之 Hybrid App
- 学习Android
- 转载Android Bug分析系列(第三方平台安装app启动后,home键回到桌面后点击app启动时会再次启动入口类bug的原因剖析)
- react-native App的原理介绍