其实很多用户感觉到的卡顿问题最主要的根源是来自渲染性,在开发过程当中,大家在和UI沟通的时候,能感觉到这些UI特别希望他们的APP能有更绚丽的动画,更精美的设计,同时还用一些很大的图片来展现时尚元素,来增加用户的体验.但是UI设计师是站在UI的角度来考虑问题的,他们不知道安卓系统有时候可能无法完成复杂的界面渲染操作.我们知道安卓的系统,它每隔16毫秒它会发出信号,触发对UI进行渲染,如果每次渲染都成功,这样就能达到流畅的画面所需要的60fps,也就是每秒60帧.为了能够实现60fps,这就意味着程序的大多数操作必须在16毫秒内完成,也就是拿1000除以60,约等于16毫秒.在执行一些动画,或者listview,recycleview滑动的时候,通常能感觉到,有时候会有一些卡顿和不流畅.这就是这里的操作很复杂,然后产生的丢帧现象导致的卡顿.其实有很多原因会造成卡顿.就简单举例,拿listView来说,listView的item和layout如果太过于复杂.它就无法完成在16毫秒内的渲染,也有可能是你的item上层叠了太多的bindground,其它太多的imageview.甚至还有可能是动画执行的次数过多,这些都会造成cpu和gpu的负载过重,具体到我们60fps->16ms,我们想,为什么要把标准设置到60fps,首先我们要知道人脑对于画面的连贯性,它是有一定的限制的,对于手机来说,我们需要感知屏幕操作连贯性,而安卓系统把这种流畅的贞率规定在60fps,60fps就是每秒实现的帧数,换算过来就是60毫秒一帧,也就是一秒60帧,所以说为了保证不丢失帧数,我们一定要在16毫秒内处理完这次所有的cpu和gpu的计算,绘制和渲染操作,所以说UI卡顿它是可以进行量化的,每一次能否成功渲染是非常重要的,16毫秒是一个很大的一个标准,能否完整的做完一个操作,直接决定了卡顿性能问题,同时我们还需要知道,每次虚拟机进行GC的时候,所有的线程它都会暂停,当GC完了之后,所有的线程才能够继续进行,也就是说当这个16毫秒内进行渲染的时候,正好如果遇到了大量的GC操作,会导致渲染时间不够,从而导致卡顿问题,大量的GC,我们知道也会造成内存抖动,所以说GC对象内存分配需要进行考虑的.
overdraw中文意思是过度绘制,它的意思是在屏幕上它的某个像素在同一帧的时间内被绘制了很多次,经常出现在多层次的UI结构里面,如果有时候你把UI设置成GONE和invisible的时候,它也会做绘制的操作,这就导致了某些像素会被绘制很多次,这样就浪费了CPU和GPU的资源,我们在开发过程中,如果老大给你分配一个,这时候让你解决一个UI卡顿问题,你肯定回去用我们手机当中的GPU选项,也就是开发者选项当中,大家可以观察overdraw情况,有蓝色,淡绿色,淡红色,深红色.大家主要的目标就是要减少红色,尽量出现蓝色,最后我给大家强调一下,overdraw,overdraw出现的原因就是说你的UI布局当中有大量的重叠的部分,还有的时候,是非必要重叠背景,举例一下,就是activity中有一个背景,如果里面的layout有自己的背景,同时layout中的子View又有自己的背景,这时候,你仅仅通过移除非必须的背景图片,就能够减少红色的overdraw区,就能够提升程序的性能,减少UI的卡顿.
我来总结一下UI卡顿的原理,UI卡顿这些性能问题主要根源来自与安卓系统的渲染性能做了太多的耗时操作,做了太多耗时操作原因,有可能是你的layout 太复杂,也有可能是你的UI上层叠了太多其它layout布局,还有就是你的动画执行次数过多,
我们知道安卓不是线程安全的,所以说你的耗时操作不能在主线程中进行操作的,你必须开启一个工作线程来执行异步任务,我们大家知道经常用到的进程通讯handle,它可以进行线程之间消息的发送,所以说你在UI线程中做了轻微的耗时操作,注意是轻微的,不少耗时操作,如果你做了耗时操作,就不仅仅是UI卡顿了,UI卡顿其实是轻量版的ANR.如果你做了耗时操作你就会导致ANR,所以说这里的前提是你在UI线程中做了会引起卡顿但是不至于ANR的轻微耗时操作,但是这也是很影响性能的,所以大家一定要避免.
这里关于Layout布局,其实我们在开发的时候,其实我们可以和UI设计师很好的去平衡这个布局真正要显示的内容,要知道他们的痛点,开发时,一定要注意背景布局一定不能重叠,background的设置和子view的设置一定要谨慎考虑不能造成UI卡顿.
我们知道绚丽的动画是可以吸引用户,拉高你的存活,但是对我们手机的性能也是一个很大的考验,所以说对动画的选择,我们需要仔细的平衡一下
这个其实和第二点是类似的,第二点是从布局上,这一点是从代码上考虑的,这是大家一定需要注意的.
我们知道View的绘制大体是需要经过,measure,layout和draw这三个方法l来进行的,所以说如果你的view如果频繁的触发了测量和绘制和摆放操作,就会导致整个性能耗时过多,从而导致它所需要的View频繁的重新渲染,而影响整个的性能
大家在平时回收内存的时候,也需要考虑一下时机.
这就是说在开发过程中,一些代码的逻辑,代码的启动顺序,以及一些异步任务的处理,它会导致整个的UI卡顿.所以说对于代码的优化也能影响卡顿,所以说良好的代码开发习惯,也能提高APP的性能
它产生的主要原因就是在主线程中做了耗时操作导致,程序不能响应,这是急需要避免的.应该说UI卡顿是轻量版的ANR, 但是不是说因为它是轻量版的,就可以造成的,它还是需要避免的.
* 选择耗费性能少的布局
耗费性能低的布局有FrameLayout、LinearLayout
高的有RelativeLayout;布局过程消耗更多的CPU资源和时间
嵌套消耗的性能 > 单个布局本身的性能
* 减少布局的嵌套
布局层级越少绘制的工作量越少,绘制速度越快,可以选择merge标签,减少布局层级。
<merge>作为布局的根标签时,其他布局通过<include>标签引用的时候,只有merge标签内的内容被引用,这样减少一层布局。
* 提供布局的复用性
提取不同布局之间共同的部分,提高布局的测量和绘制时间,使用include标签。
* 减少初次测量&绘制时间
使用<ViewStub>标签,轻量级View,不占用显示资源,默认不显示,只有在需要时才显示,比如显示进度、信息出错的提示布局。
ViewStub标签引入外部布局,类似于include标签。当调用ViewStub.inflate()时,ViewStub指向的布局文件才会被inflate、实例化,显示出来。
注意:
~ ViewStub的layout布局不能使用merge标签,否则报错
~ ViewStub的inflate只能执行一次,显示之后,就不能在使用ViewStub控制。
~ 与View.setVisible(View.Gone)的区别:View 的可见性设置为 gone 后,在inflate 时,该View 及其子View依然会被解析;而使用 ViewStub就能避免解析其中指定的布局文件,从而节省布局文件的解析时间 & 内存的占用
* 尽可能少用布局属性wrap_content
布局属性 wrap_content 会增加布局测量时计算成本
布局调优工具
常用的有hierarchy viewer、Lint、Systrace
Hierarchy Viewer
Android studio提供的UI性能检测工具。
这体现在listview上,我们尽量复用listView的adapter当中的gitview()方法,不要重复获取实例,使用convertView,列表在滑动的时候你不要进行元素的更新,就是说在listView滑动的时候,你监听它的事件只有它的滑动到停止的时候,你才可以去加载图片,加载数据,而它在滑动的时候,你可以只显示图片的默认值,或者缩略图
就是说,你一定要尽量减少整个布局当中,不必要的背景设置,图片你要进行压缩处理,还有就是对GC的处理上,一定要注意当图片16ms内进行渲染的时候,所造成的一些频繁GC回收,从而导致内存泄漏问题
不要在主线程做耗时操作,一定要开启子线程去做耗时操作,常用我们安卓中提供的良好的异步消息处理框架,例如: