性能优化实践(三)-卡顿优化思考

一、基本概念

Android系统每隔16ms发出VSYNC信号,触发对UI进行渲染,如果每次渲染都成功,这样就能够达到流畅的画面所需要的60fps,这也意味着程序的大多数操作都必须在16ms内完成。如果无法完成,则发生丢帧,上一帧画面被重复显示,造成卡顿的视觉。


从整个视图渲染流程看:

Surfaceflinger由init启动的独立进程,提供合成视图的系统服务。如果Surfaceflinger挂掉,会重启zygote。

在Surfaceflinger的init方法中,实例化了HWComposer和两个EventThread。

HWComposer:负责输出硬件产生或软件模拟的Vsync信号。

EventThread:负责分发vsync到Choreographer和SurfaceFlinger。其中mEventThread对应Choreographer;而mSFEventThread:对应SurfaceFlinger。

VSYNC信号主要的两个订阅者:SurfaceFlinger 和 Choreographer。

SurfaceFlinger:接收信号执行合成Layer流程。

Choreographer:接收信号来控制同步处理输入(Input)、动画(Animation)、绘制(Draw)三个UI操作。

Choreographer通知应用层绘制、SurfaceFlinger负责合成视图、两者之前加上了一定的offset,这样能保证两者步调一致。

在这个过程中,CPU负责把视图加工为多边形和纹理。GPU负责把多边形和纹理做栅格化处理,成为送显的像素数据。

二、造成卡顿的原因
应用层面:

1 视图层面的问题

包括layout层级太深View太多、View太复杂、重复绘制、ListView没优化、动画设计不合理等等。

这是遇到卡顿问题首先需要排查的,部分问题可以通过开发阶段的coding规范来避免的。

1)layout层级太深View太多:可以通过Lint来检测,优化:通过合理容器的使用,优先减少层级,其次减少View数目,能重用的尽量重用。

2)View太复杂:如果是自定义View,那还是从视图太深、View太多两个层面来考虑优化。如果是成熟的View:比如WebView、VideoView这种重量级的View,尽量复用和管理好生命周期。

3)重复绘制:通过Settings中打开GPU过度绘制 & GPU呈现模式可以了解当前视图层级关系,当然这部分与前面两点也是分不开的,最基本的要注意移除xml中非必须背景。

4)ListView优化,这部分主要是convertView的复用,能减少View的创建;ViewHolder的使用,减少View的find和赋值,加快加载速度;分页加载:控制一次加载的数据量,这样加载速度会快,内存压力也相对小。

5)动画:合理设计动画,能不用帧动画尽量不用,因为图片比较占内存,尤其是数量多的时候。另外针对属性动画,同一个view的一系列动画,可以使用Keyframe+PropertyValuesHolder组合方式达到只使用一个ObjectAnimator,多个view的动画用AnimatorSet进行动画组合和排序。

2 消息相关耗时

我们都知道,耗时操作放到子线程做,通过handle返回主线程更新UI。但是消息本身也是会耗时的,主要分两方面:1)消息本身执行耗时, 2)消息执行被delay。消息本身执行耗时那就是主线程耗时,消息执行被delay,在messageQueue中,由于之前的Message太多或者执行时间过长,导致当前需更新UI的操作得不到及时处理,尤其是16.6ms硬性标准下,一旦delay必然丢帧。

3 主线程耗时

这部分我要说的并不是在主线程做耗时操作了,而是站在CPU调度的角度来看耗时问题,也就是说,比如主线程有500ms的耗时,要么Running了多久,是否存在Sleeping和Uninterruptible sleep等状态,这段时间内CPU被抢占了压根就没腾出功夫来执行你这操作。如果有现场的话,通过抓systrace能比较明显看出来。

4 Input事件本身耗时

在Android整个Input体系中有三个重要的成员:Eventhub,InputReader,InputDispatcher。它们分别担负着各自不同的职责,Eventhub负责监听/dev/input产生Input事件,InputReader负责从Eventhub读取事件,并将读取的事件发给InputDispatcher,InputDispatcher则根据实际的需要具体分发给当前手机获得焦点实际的Window,最终交给ActivityThread通过消息来处理。

系统角度:
InputDispatcher分发事件给Window这个过程是跨进程通信,获取对应window本身可能存在耗时。

应用角度:
客户端接收事件的消息本身又可能存在耗时和delay的情况,这又回到消息耗时的范畴了。

5 持锁耗时
这属于业务逻辑层面的问题,最简单的就是主线程死锁,亦或是主线程在等锁,然后当前锁被其他线程持有在做耗时操作等等。

6 频繁GC

我们知道,执行GC操作的时候,所有线程的任何操作都会需要暂停,等待GC操作完成之后,其他操作才能够继续运行。通常来说,单个的GC并不会占用太多时间,但是大量不停的GC操作则会显著占用帧间隔时间(16ms)。如果在帧间隔时间里面做了过多的GC操作,那么自然其他类似计算,渲染等操作的可用时间就变得少了。

导致GC频繁执行有两个原因:

1)内存抖动,在memory monitor里能很明显看出来,短时间内创建大量对象然后又迅速被释放。

比如:在一个方法里for循环拼接String。会产生大量废弃的String对象,短时间内又会被回收,所以容易造成抖动,可以用StringBuilder/StringBuffer来替代,它们实现是动态数组,初始长度128,不够用了通过arraycopy来增加长度。对象统一管理,不会短时间内造成短时间内大量创建和销毁的问题,同时append与+相比更安全。

String:适用于少量的字符串操作的情况
StringBuilder:适用于单线程下在字符缓冲区进行大量操作的情况
StringBuffer:适用多线程下在字符缓冲区进行大量操作的情况

2)瞬间产生大量的对象会严重占用Young Generation的内存区域,当达到阀值,剩余空间不够的时候,也会触发GC。即使每次分配的对象占用了很少的内存,但是他们叠加在一起会增加Heap的压力,从而触发更多其他类型的GC。这个操作有可能会影响到帧率,并使得用户感知到性能问题。

系统层面:

1 内存原因

在系统内存非常低的情况下,常规经验是:MemAvailable 低于MemTotal 1/10的情况下,容易出现内存引起的卡顿,原因无非就是在内存低的情况下内核在分配内存时,很难从物理内存(伙伴系统)直接拿到合适大小的页面,此时会触发回收操作,如内存整理(compact)、回收匿名页(swap)、回收文件页(dirty=回写,clean=丢弃)等操作。这些回收操作较慢,因此耗时。这个过程主要体现在新启一个应用,zygote fork进程申请内存的时候。

2 系统服务持锁耗时

应用binder call请求系统服务,一般来说,系统服务如AMS、WMS对应的方法,一上来先不管三七二十一,就是一把大锁,很多情况下,特定的操作会造成持锁耗时的情况,具体问题具体分析。

3 CPU调度问题

这类情况不太多见,但是也是存在的。在某个绘制周期中,CPU被抢占,无法及时开始绘制操作。这分几部分来看,首先是不是被某个进程抢占的,比如dex2oat。或者看这段时间CPU使用率非常高,但是可能是大核跑满了,但是小核相对比较闲,这属于系统调度有问题等等。

例如:dex2oat发生的时候,占用所有有CPU(默认策略是有多少个核,就启动多少个线程),会将原文件中的dex文件抽出来,逐个指令的判断,然后进行翻译,并生成大量的中间内容,这些在memory当中是保存不下的,所以采用了swap机制, memory越少,越容易发生交换,所以还可能引起IO上的瓶颈。

可以设置系统属性:dalvik.vm.bg-dex2oat-threads 和 dalvik.vm.dex2oat-threads ,这两个系统属性是分别设置在前后台执行dex2oat限制的线程数,对应8核CPU来说,比如设置前后台分别为4,这样dex2oat执行时间会变长,但是卡顿会被缓解。

当然还有一种情况是,当手机温度过高,导致CPU降频,也会出现系统卡顿。

本文只是对卡顿分析提供一点不成熟的小思路。随着学习的深入,我会持续更新。
文中牵涉到的布局和重复绘制相关的内容可以参考我的文章:布局优化
文中牵扯到的相关性能优化工具可以参考我的系列文章:性能优化工具篇总结

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

推荐阅读更多精彩内容