您的位置:首页 > 移动开发 > Android开发

Android开发界面显示慢-过度绘制优化

2017-02-06 10:42 288 查看
本文原文链接:http://www.cnblogs.com/liuling/p/2015-10-08-2.html

作者名:残剑

如果一个布局十分复杂,那么就需要来排查是否出现了过度绘制,如果出现了,那么很可能会造成刷新率下降,造成卡顿的现象。那么什么是过度绘制呢?过度绘制就是在同一个区域中叠加了多个控件。这就像小时候我们画画,白纸就是没有绘制的画板,如果我们画了一个房子,涂上了红色,又在上面画了窗户,图上了棕色,窗户上又画了蓝色的玻璃,这重重复的叠加就是过度绘制,在白纸上的结果是,过度绘制的区域纸会被水笔浸的比较湿,在手机上就会出现显示较慢。如果说这是感性的认识,那么我就引用下面一段话来理性的解释一下:

1. 布局文件是一个xml文件,inflate布局文件其实就是解析xml,根据标签信息创建相应的布局对象并做关联。xml中的标签和属性设置越多,节点树的深度越深,在解析时要执行的判断逻辑、函数的嵌套和递归就越多,所以时间消耗越多;

2. inflate操作只是布局影响的第一个环节,一个界面要显示出来,在requestLayout后还要执行一系列的measure、layout、draw的操作,每一步的执行时间都会受到布局本身的影响。而界面的最终显示是所有这些操作完成后才实现的,所以如果布局质量差,会增加每一步操作的时间成本,最终显示时间就会比较长。

现在,我们就来说说如何查看是否有过度绘制,和如何避免它吧。

 

一、查看是否存在过度绘制

1. GPU过渡绘制:对于过度绘制的测试主要通过人工进行测试,也是发现应用过渡绘制的首选途径 .通过打开开发者选项中的 显示GPU过度绘制(魅族手机:设置—辅助功能–开发人员工具–硬件加速渲染—调试GPU过渡绘制—
显示过渡绘制区域.)来进行测试(PS:只有android4.2及以上的版本才具备此功能)

1. 颜色标识: 从好到差:蓝-绿-淡红-红

1. 蓝色1x过度绘制

2. 绿色2x过度绘制

3. 淡红色3x过度绘制

4. 红色超过4x过度绘制

2. 验收标准:

1. 控制过度绘制为2x

2. 不允许存在4x过度绘制

3. 不允许存在面积超过屏幕1/4区域的3x过度绘制(淡红色区域)



从图中我们就可以看到,文字部分出现了绿色(因为和底部的蓝色叠加了,所以变成了黄绿色),在顶部开关部分出现了红色,也就是四层的过度绘制,这是需要避免的。但由于在屏幕上占的位置很小,所以可以酌情考虑。

 



上面面是小米商店的截屏,可以看见其中有大量的过度绘制区域,总结下来过度绘制较常见于文字区域。

 

二、避免过度绘制的方法

下面这段文字来自他人博客:

作者:Gracker

出处:androidperformance.com

本文版权归作者所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

打赏一下: 微博打赏

1. 尽量多使用RelativeLayout和LinearLayout, 不要使用绝对布局AbsoluteLayout,

1. 在布局层次一样的情况下, 建议使用LinearLayout代替RelativeLayout, 因为LinearLayout性能要稍高一点.

2. 在完成相对较复杂的布局时,建议使用RelativeLayout,RelativeLayout可以简单实现LinearLayout嵌套才能实现的布局.

2. 将可复用的组件抽取出来并通过include标签使用;

3. 使用ViewStub标签来加载一些不常用的布局;

4. 动态地inflation view性能要比SetVisiblity性能要好.当然用VIewStub是最好的选择.

5. 使用merge标签减少布局的嵌套层次

6. 去掉多余的背景颜色

7. 对于有多层背景颜色的Layout来说,留最上面一层的颜色即可,其他底层的颜色都可以去掉

8. 对于使用Selector当背景的Layout(比如ListView的Item,会使用Selector来标记点击,选择等不同的状态),可以将normal状态的color设置为”@Android:color/transparent”,来解决对应的问题

9. 内嵌使用包含layout_weight属性的LinearLayout会在绘制时花费昂贵的系统资源,因为每一个子组件都需要被测量两次。在使用ListView与GridView的时候,这个问题显的尤其重要,因为子组件会重复被创建.所以要尽量避免使用Layout_weight

10. 使得Layout宽而浅,而不是窄而深(在Hierarchy Viewer的Tree视图里面体现)

 

上面提到的多个工具和技巧我都在之前的文章有所讲解了,在实际开发过程中需要多多思考,根据情况来使用不同的技巧。

首先将讲解一下GPU过渡绘制,也是开发者最直接接触的部分吧,这个内容将分为两个部分来将讲,第一部分初步讲解一下gpu过渡绘制的原理,和一些优化建议,第二部分将用实际例子来讲解优化GPU过渡绘制的一般步骤。


过渡绘制概念

GPU过渡绘制的概念:GPU过度绘制指的是在屏幕一个像素上绘制多次(超过一次),比如一个TextView后有背景,那么显示文本的像素至少绘了两次,一次是背景,一次是文本。GPU过度绘制或多或少对性能有些影响,设备的内存带宽是有限的,当过度绘制导致应用需要更多的带宽(超过了可用带宽)的时候性能就会降低。带宽的限制每个设备都可能是不一样的。


过渡绘制的原因

太多的View叠加

复杂的层级叠加

更长的inflation时间


过渡绘制和不合理的xml布局的影响

布局文件是一个xml文件,inflate布局文件其实就是解析xml,根据标签信息创建相应的布局对象并做关联。xml中的标签和属性设置越多,节点树的深度越深,在解析时要执行的判断逻辑、函数的嵌套和递归就越多,所以时间消耗越多;

inflate操作只是布局影响的第一个环节,一个界面要显示出来,在requestLayout后还要执行一系列的measure、layout、draw的操作,每一步的执行时间都会受到布局本身的影响。而界面的最终显示是所有这些操作完成后才实现的,所以如果布局质量差,会增加每一步操作的时间成本,最终显示时间就会比较长。


过渡绘制的一些基本概念:

Android提供了三个工具来帮助辨别和解决重绘问题:Hierachy Viewer,Tracer for OpenGL和Show GPU overdraw。前两个可以在ADT工具或者独立的monitor工具中找到,最后一个是在开发者选项的一部分.

GPU过渡绘制测试:对于过度绘制的测试主要通过人工进行测试,也是发现应用过渡绘制的首选途径 .通过打开开发者选项中的 显示GPU过度绘制(魅族手机:设置—辅助功能–开发人员工具–硬件加速渲染—调试GPU过渡绘制— 显示过渡绘制区域. (魅族手机需要打开开发者模式:需要在电话界面输入: ##6961## ))
来进行测试(PS:只有android4.2及以上的版本才具备此功能)

颜色标识: GPU过渡绘制从好到差:蓝-绿-淡红-红

蓝色1x过度绘制

绿色2x过度绘制

淡红色3x过度绘制

红色超过4x过度绘制

验收标准:

控制过度绘制为2x

不允许存在4x过度绘制

不允许存在面积超过屏幕1/4区域的3x过度绘制(淡红色区域)


优化工具介绍

Lint工具:

Eclipse中,点击即可,下面的窗口中会出现提示,根据提示和具体解决办法消除.

Android Studio自带Lint工具,不合理或者需要优化和注意的地方,会用黄色标记出来.

Lint工具不仅对布局有很好的优化建议,对代码中不合理的活着存在潜在风险的模块也会提出优化建议,所以一个好的建议是:多使用Lint工具检查自己的应用,尽量消除所有的建议.

Lint工具可以用命令行来运行,具体使用可以参考:tools.android.com




Lint工具的提升例子(摘自官方文档):

Use compound drawables(使用compound drawables) - A LinearLayout which contains an ImageView and a TextView can be more efficiently handled as a compound drawable.

Merge root frame(使用Merge根框架) - If a FrameLayout is the root of a layout and does not provide background or padding etc, it can be replaced with a merge tag which is slightly more efficient.

Useless leaf(去除无用的分支) - A layout that has no children or no background can often be removed (since it is invisible) for a flatter and more efficient layout hierarchy.

Useless parent (去除无用的父控件)- A layout with children that has no siblings, is not a ScrollView or a root layout, and does not have a background, can be removed and have its children moved directly into the parent for a flatter and more efficient layout hierarchy.

Deep layouts (注意Layout的深度) - Layouts with too much nesting are bad for performance. Consider using flatter layouts such as RelativeLayout or GridLayout to improve performance. The default maximum depth is

Hierarchy Viewer:此工具是一个ADT工具(或者monitor,最新版本的SDK建议不使用独立的HV工具,而是直接在monitor中进行操作.)的一部分,可以被用作对视图层级进行快速解读。在处理布局问题时特别有用,对于性能问题也很适用。Hierarchy Viewer默认只能在非加密设备使用,例如工程机,工程平板或者模拟器。为了能够在任何手机上使用Hierarchy Viewer,你得在你的应用中添加ViewServer,这是一个开源库,使用方法可以参考这里。连接上设备,打开Hierarchy
Viewer(定位到tools/目录下,直接执行hierarchyviewer的命令,选定需要查看的Process,再点击Load View Hierarchy会显示出当前界面的布局Tree。在每个模块的Traffic light上有三个灯,分别代表了Measure, Layout and Draw三个步骤的性能。


布局优化建议

在Android UI布局过程中,通过遵守一些惯用、有效的布局原则,我们可以制作出高效且复用性高的UI,概括来说包括如下几点:

尽量多使用RelativeLayout和LinearLayout, 不要使用绝对布局AbsoluteLayout,

在布局层次一样的情况下, 建议使用LinearLayout代替RelativeLayout, 因为LinearLayout性能要稍高一点.

在完成相对较复杂的布局时,建议使用RelativeLayout,RelativeLayout可以简单实现LinearLayout嵌套才能实现的布局.

将可复用的组件抽取出来并通过include标签使用;

使用ViewStub标签来加载一些不常用的布局;

动态地inflation view性能要比SetVisiblity性能要好.当然用VIewStub是最好的选择.

使用merge标签减少布局的嵌套层次

去掉多余的背景颜色(查看背景颜色是否多余,可以将HierarchyView中的图导出为psd文件,然后用Photoshop查看.具体可以参考这个视频)

对于有多层背景颜色的Layout来说,留最上面一层的颜色即可,其他底层的颜色都可以去掉

对于使用Selector当背景的Layout(比如ListView的Item,会使用Selector来标记点击,选择等不同的状态),可以将normal状态的color设置为\”@android:color/transparent”,来解决对应的问题

内嵌使用包含layout_weight属性的LinearLayout会在绘制时花费昂贵的系统资源,因为每一个子组件都需要被测量两次。在使用ListView与GridView的时候,这个问题显的尤其重要,因为子组件会重复被创建.所以要尽量避免使用Layout_weight

使得Layout宽而浅,而不是窄而深(在Hierarchy Viewer的Tree视图里面体现)


源码相关

另外有能力看源码的同学,下面是绘制OverDraw的源码位置:/frameworks/base/libs/hwui/OpenGLRenderer.cpp,有兴趣的可以去研究研究。
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
41
42

void OpenGLRenderer::renderOverdraw() {
if (mCaches.debugOverdraw && getTargetFbo() == 0) {
const Rect* clip = &mTilingClip;

mCaches.enableScissor();
mCaches.setScissor(clip->left, mFirstSnapshot->height - clip->bottom,
clip->right - clip->left, clip->bottom - clip->top);

// 1x overdraw
mCaches.stencil.enableDebugTest(2);
drawColor(mCaches.getOverdrawColor(1), SkXfermode::kSrcOver_Mode);

// 2x overdraw
mCaches.stencil.enableDebugTest(3);
drawColor(mCaches.getOverdrawColor(2), SkXfermode::kSrcOver_Mode);

// 3x overdraw
mCaches.stencil.enableDebugTest(4);
drawColor(mCaches.getOverdrawColor(3), SkXfermode::kSrcOver_Mode);

// 4x overdraw and higher
mCaches.stencil.enableDebugTest(4, true);
drawColor(mCaches.getOverdrawColor(4), SkXfermode::kSrcOver_Mode);

mCaches.stencil.disable();
}
}

void OpenGLRenderer::countOverdraw() {
size_t count = mWidth * mHeight;
uint32_t* buffer = new uint32_t[count];
glReadPixels(0, 0, mWidth, mHeight, GL_RGBA, GL_UNSIGNED_BYTE, &buffer[0]);

size_t total = 0;
for (size_t i = 0; i < count; i++) {
total += buffer[i] & 0xff;
}

mOverdraw = total / float(count);

delete[] buffer;
}

还有QA可能用得到的一个指标:OverDraw数值,这个的源码位置在Framework/base/core/Java/android/view/HardwareRender.java中(5.0中去掉了这个数值的显示)
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

private void debugOverdraw(View.AttachInfo attachInfo, Rect dirty,
HardwareCanvas canvas, DisplayList displayList) {

if (mDebugOverdraw == OVERDRAW_TYPE_COUNT) {
if (mDebugOverdrawLayer == null) {
mDebugOverdrawLayer = createHardwareLayer(mWidth, mHeight, true);
} else if (mDebugOverdrawLayer.getWidth() != mWidth ||
mDebugOverdrawLayer.getHeight() != mHeight) {
mDebugOverdrawLayer.resize(mWidth, mHeight);
}

if (!mDebugOverdrawLayer.isValid()) {
mDebugOverdraw = -1;
return;
}

HardwareCanvas layerCanvas = mDebugOverdrawLayer.start(canvas, dirty);
countOverdraw(layerCanvas);
final int restoreCount = layerCanvas.save();
layerCanvas.drawDisplayList(displayList, null, DisplayList.FLAG_CLIP_CHILDREN);
layerCanvas.restoreToCount(restoreCount);
mDebugOverdrawLayer.end(canvas);

float overdraw = getOverdraw(layerCanvas);
DisplayMetrics metrics = attachInfo.mRootView.getResources().getDisplayMetrics();

drawOverdrawCounter(canvas, overdraw, metrics.density);
}
}

private void drawOverdrawCounter(HardwareCanvas canvas, float overdraw, float density) {
final String text = String.format(\"%.2fx\", overdraw);
final Paint paint = setupPaint(density);
// HSBtoColor will clamp the values in the 0..1 range
paint.setColor(Color.HSBtoColor(0.28f - 0.28f * overdraw / 3.5f, 0.8f, 1.0f));

canvas.drawText(text, density * 4.0f, mHeight - paint.getFontMetrics().bottom, paint);
}


参考文章

优化过程

反编译并添加gpu显示

http://developer.android.com/training/improving-layouts/optimizing-layout.html#Inspect

http://developer.android.com/training/improving-layouts/reusing-layouts.html

http://developer.android.com/training/improving-layouts/loading-ondemand.html

http://developer.android.com/training/improving-layouts/smooth-scrolling.html

http://developer.android.com/tools/help/hierarchy-viewer.html

http://tools.android.com/tips/lint

做设计的人很少能知道GPU过度绘制是个什么鬼?跟设计有什么关系?今天就让大家了解了解,首先来普及一下Android开发者选项中的Debug GPU overdraw

GPU过度绘制定义

如果你粉刷过一个房间或一所房子,就会知道给墙壁涂上颜色需要做大量的工作。假如你还要重新粉刷一次的话,第二次粉刷的颜色会覆盖住第一次的颜色,第一次的颜色就永远不可见了,等于你第一次粉刷做的大量工作就完全被浪费掉。这太可怕了。

同样的道理,如果在你的应用程序中浪费精力去绘制一些东西同样会产生性能问题。过度绘制这个名词就是用来描述屏幕上一个像素在单个帧中被重绘了多少次。

GPU过度绘制就指的是在屏幕一个像素上绘制多次(超过一次),GPU过度绘制或多或少对性能有些影响。

GPU过度绘制分析

过度绘制其实是一个性能和设计的交叉点。我们在设计上追求很华丽的视觉效果,但一般来说这种视觉效果会采用非常多的层叠组件来实现,这时候就会带来过度绘制的问题。我们再来看看具体显示在Android界面层级关系:

当我们来绘制一个界面时,会有一个windows,然后是建立Activity,在Activity里可以建立多个view,或view group,view也可以嵌套view。这些组件从上到下分布,上面的组件是可以被用户看见的,而在下面的组件是不可见的,但是我们依然要花很多时间去绘制那些不可见的组件,因为在某些时候,它也可能会显示出来。



检测过度绘制

如何查看是否过度绘制:

设置-开发者选项-调试GPU过度绘制-显示过度绘制区域(过度渲染等,不同机器可能不同)



然后就可以看看你的应用是否存在过度绘制的情况了。

那么如何判断界面是否存在过度绘制呢?

开启后,点击我们的应用,可以看到各种颜色的区域,其中:



最理想的是蓝色,一个像素只绘制一次,合格的页面绘制是白色、蓝色为主,绿色以上区域不能超过整个的三分之一,颜色越浅越好。

那么从设计的角度来看你的应用是否GPU绘制过度,看一下以下几个界面:



从上图我们可以看出:

Google now页面GPU绘制比较正常基本都是在1x-2x范围内,QQ的绘制情况也还可以,2345手机助手和2345影视大全过度绘制是很严重的,基本都是超过3x,4x。

可能有些人觉得不以为然,觉得没什么影响。话又说回来,GPU绘制过渡对应用造成什么影响。

实际上,GPU绘制影响的是界面的流畅度和用户体验,对于好的手机可能体验不到差距,对于差的手机,流畅度却起着关键的作用。

可以大部分设计师关注的都是开发是否有100%还原你的设计稿,应用的交互体验是否良好,没有几个设计师会去关注GPU过度绘制问题。

本人接触到这方面知识也是优化我们开发人员指导,当时我们正在做界面层级简化,而应用界面出现了2种背景颜色,如图(左边的背景为白色,右边的背景为浅灰)。



开发让我们梳理一下页面层级,得出右边的管理页面要再多绘制一层;



开发人员建议我们以后在设计中,梳理一下页面之前层级关系,尽量保持整个界面的架构统一,大背景色一致性。而且开发人员在开发过程中,尽量用简化的结构来布局,保持界面还原度的同时也要考虑界面的流畅度。

列举一下我们2345王牌助手首页界面前后优化对比,看了以后,感觉整个人的心情都好了有木有啊,感觉更高大上了很多。



经过优化后的页面一次绘制时间能提升3ms-5ms,可以看下面这张图来进行对比,绿色色块部分为提升空间。



这次优化工作对我来说,是一次宝贵的经验,我们一直在努力让设计变得更好,但有时候设计不只是表面的美化工作,还是深度的改善用户体验。在这里我想感慨一下,一个项目的成功上线,里面不知道有多少酸甜苦辣,不知道有多少人在为之努力,感谢所有工作人员的不懈努力,辛苦你们啦!

总结原因

1.太多重叠的背景

重叠着的背景有时候是有必要的,有时候是没必要的。这要视你的项目具体情况而定。

2.太多叠加的View

或者本来这个UI布局就很复杂或者你是为了追求一个炫丽的视觉效果,这都有可能使得很多view叠加在一起。这个情况非常普遍,下面的建议中会谈谈怎么减少这种情况带来的影响。

3.复杂的Layout层级

复杂的层级关系,这个在布局中也很常见,下面也会说这种情况怎么做可以尽可能的减少过度绘制。

建议:

1.太多重叠的背景

这个问题其实最容易解决,建议前期在设计时尽量保持整体背景统一,另外开发可以检查你在布局和代码中设置的背景,有些背景是被隐藏在底下的,它永远不可能显示出来,这种没必要的背景一定要移除,因为它很可能会严重影响到app的性能。

2.太多重叠的view

第一个建议是:使用ViewStub来加载一些不常用的布局,它是一个轻量级且默认不可见的视图,可以动态的加载一个布局,只有你用到这个重叠着的view的时候才加载,推迟加载的时间。第二个建议是:如果使用了类似viewpager+Fragment这样的组合或者有多个Fragment在一个界面上,需要控制Fragment的显示和隐藏,尽量使用动态地Inflation view,它的性能要比SetVisiblity好。

3.复杂的Layout层级

这里的建议比较多一些,首先推荐用Android提供的布局工具Hierarchy Viewer来检查和优化布局。第一个建议是:如果嵌套的线性布局加深了布局层次,可以使用相对布局来取代。第二个建议是:用标签来合并布局,这可以减少布局层次。第三个建议是:用标签来重用布局,抽取通用的布局可以让布局的逻辑更清晰明了。记住,这些建议的最终目的都是使得你的Layout在Hierarchy Viewer里变得宽而浅,而不是窄而深。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
相关文章推荐