实战 Android中的UI过度绘制

相信很多人都有这种经历,在使用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对象,会造成内存泄漏,因此需要将其提取为全局变量。

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,565评论 6 479
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,021评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,003评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,015评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,020评论 5 370
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,856评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,178评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,824评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,264评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,788评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,913评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,535评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,130评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,102评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,334评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,298评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,622评论 2 343

推荐阅读更多精彩内容