交互是用户体验最直接的方面,交互场景大致可以分为四个部分: ui 呈现、应用程序启动、页面跳转和事件响应。对于以上四个方面,可以从以下两个方面进行优化:
主要原因是绘制的层级深、页面复杂、刷新不合理,由于这些原因导致卡顿的场景出现在UI和启动后的初始界面以及跳转到页面的回执上。
数据处理:导致这种卡顿场景的原因是数据处理量太大,一般分为三种情况,
一是数据处理在UI线程(这种应该避免),
二是数据处理占用CPU高,导致主线程拿不到时间片,
三是内存增加导致GC频繁,从而引起卡顿。
我们知道Android的绘制步骤是:Measure、Layout、Draw,所以布局的层级越深、元素越多、耗时也就越长。还有就是Android系统每隔16ms发出VSYNC信号触发对UI进行渲染如果每次渲染都成功这样就能达到流畅的画面所需的60FPS。如果某个操作花费的时间是24ms 系统在得到VSYNC信号时就无法进行正常渲染这样就发生了丢帧现象。
棉白杨现象有两个原因:
绘制任务太重,绘制一帧内容耗时太长。
根据问题的原因,我们可以通过以下方式进行优化:
布局优化
在Android种系统对View进行测量、布局和绘制时,都是通过对View数的遍历来进行操作的。如果视图的高度过高,会严重影响测量、布局和渲染的速度。Google也在API文档中建议View树的高度不宜超过10层。使用RelativeLayout替代LineraLayout作为默认根布局降低LineraLayout嵌套产生布局树的高度从而提高UI渲染的效率。
在布局优化方面,我们可以通过以下方式进行优化:
布局复用,使用标签重用layout;
提高显示速度,使用延迟View加载;
减少层级,使用标签替换父级布局;
注意使用wrap_content,会增加measure计算成本;
删除控件中无用属性;
透支是指屏幕上的一个像素在同一帧中被绘制多次。在多层次重叠的UI结构中如果不可见的UI也在做绘制的操作就会导致某些像素区域被绘制了多次从而浪费了多余的CPU以及GPU资源。我们可以通过开启手机的透支功能来检查页面是否透支。
为了避免过度绘制,我们可以从以下几个方面进行优化:
移除XML中非必须的背景,移除Window默认的背景、按需显示占位背景图片。
自定义View优化,使用 canvas.clipRect()来帮助系统识别那些可见的区域,只有在这个区域内才会被绘制。
应用一般都有闪屏页,优化闪屏页的UI布局,可以通过ProfileGPURendering检测丢帧情况。
你也可以通过启动加载逻辑来优化。可以使用分布式加载、异步加载和延迟加载策略来提高应用程序的启动速度。
数据准备。数据初始化分析,加载数据可以考虑使用线程初始化等策略。
Android开发中,通常是异步操作页面的,因此需要可以从刷新优化上来优化应用,主要有两个原则:
减少刷新次数;
缩小刷新区域;
在实现动画效果时,需要根据不同的场景选择合适的动画框架。在某些情况下,硬件加速可以用来提供流畅性。
在移动设备中,电池的重要性不言而喻,没有电什么也做不了。对于操作系统和设备开发者来说,功耗的优化并没有停下来追求更长的待机时间,但是对于一个应用来说,功耗的问题是不容忽视的,尤其是那些被归类为“电池杀手”的应用,最终被卸载。因此,应用程序开发人员在实现需求时需要最小化功耗。
Java虚拟机中的对象
内存泄漏
内存泄漏一般导致应用卡顿,极端情况会导致OOM,OOM的原因是因为超过内存的阈值。原因主要有两方面:
能够消耗大量内存的,绝大多数是因为图片加载。这是OOM出现最频繁的地方。图片加载,一个是控制每次加载的数量,二是保证每次滑动的时候不进行加载,滑动完进行加载。一般情况使用先进后出,而不是先进先出。不过一般我们图片加载都是使用fresco或者Glide等开源库。
下面是导致内存溢出的两种情况:
通过命令行查看内存消耗情况(adb shell dumpsys meminfo 包名 -d):
UI优化主要包括布局优化以及View的绘制优化。有时候我们打开某个软件,会出现卡顿的情况,这就是UI的问题。什么情况会导致卡顿呢?一般是如下几种情况:
可以看见,上面这些导致卡顿的原因都是我们平时开发中非常常见的。有些运行起来还可以的程序,一旦进行一些瞬时测试或压力测试,就会发现很多性能问题。
对于UI性能的优化还可以通过开发者选项中的GPU过度绘制工具来进行分析。在设置->开发者选项->调试GPU过度绘制(不同设备可能位置或者叫法不同)中打开调试后可以看见如下图(对settings当前界面过度绘制进行分析):
以下说明:
蓝色(1x过度绘制),淡绿(2x过度绘制),淡红(3x过度绘制),深红(4x过度绘制)代表了4种不同程度的Overdraw情况,我们的目标就是尽量减少红色Overdraw,看到更多的蓝色区域。
Overdraw有时候是因为UI布局存在大量重叠的部分,还有的时候是因为非必须的重叠背景。例如某个Activity有一个背景,然后里面的Layout又有自己的背景,同时子View又分别有自己的背景。仅仅是通过移除非必须的背景图片,这就能够减少大量的红色Overdraw区域,增加蓝色区域的占比。这一措施能够显著提升程序性能。
如果布局中既能采用RealtiveLayout和LinearLayout,那么直接使用LinearLayout,因为Relativelayout的布局比较复杂,绘制的时候需要花费更多的CPU时间。如果需要多个LinearLayout或者Framelayout嵌套,那么可采用Relativelayout。因为多层嵌套导致布局的绘制有大部分是重复的,这会减少程序的性能。
设置–>开发者选项–>GPU呈现模式分析–>在屏幕上显示为条形图,如图所示:
随着界面的刷新,界面上会以实时柱状图来显示每帧的渲染时间,柱状图越高表示渲染时间越长,每个柱状图偏上都有一根代表16ms基准的绿色横线,每一条竖着的柱状线都包含三部分(蓝色代表测量绘制Display List的时间,红色代表OpenGL渲染Display List所需要的时间,黄色代表CPU等待GPU处理的时间),只要我们每一帧的总时间低于基准线就不会发生UI卡顿问题(个别超出基准线其实也不算什么问题的)。
绘制优化主要是指View.onDraw方法需要避免执行大量的操作:
60dps是目前最合适的图像显示速度,也是绝大部分Android设备设置的调试频率,如果在16ms内顺利完成界面刷新操作可以展示出流畅的画面,而由于任何原因导致接收到VSYNC信号的时候无法完成本次刷新操作,就会产生掉帧的现象,刷新帧率自然也就跟着下降(假定刷新帧率由正常的60fps降到30fps,用户就会明显感知到卡顿)。
Android官方规定:Activity如果在5s内无响应事件(屏幕触摸事件或者键盘输入事件);BroadcastReceiver如果在10s内无法处理完成;Service如果20s内无法处理完成。这三种情况会导致ANR。
上面说的三种导致ANR的情况,绝大多数就是因为线程阻塞导致的。 那么应该如何处理呢?Android系统为我们提供了若干组工具类来解决此问题:
线程池
Android应用的启动方式分为三种:冷启动、暖启动、热启动,不同的启动方式决定了应用UI对用户可见所需要花费的时间长短,冷启动消耗的时间最长。基于冷启动方式的优化工作也是最考验产品用户体验的地方。
冷启动是在启动应用前,系统没有获取到当前APP的Activity、Service等等。例如,第一次启动APP,又或者说杀死进程后第一次启动。那么对比其他两种方式。冷启动自然是耗时最久的。
应用发生冷启动时,系统一定会执行下面的三个任务:
那么创建应用信息,系统就需要做:
这其中有两个creation 工作,分别为Application和Activity creation。它们均在View绘制展示之前。所以,在应用自定义的 Application类和第一个Activity类中,onCreate()方法做的事情越多,冷启动消耗的时间越长。
当应用中的Activities被销毁,但在内存中常驻时,应用的启动方式就会变为暖启动。相比冷启动,暖启动过程减少了对象初始化、布局加载等工作,启动时间更短。但启动时,系统依然会展示闪屏页,直到第一个Activity的内容呈现为止。
相比暖启动,热启动时应用做的工作更少,启动时间更短。热启动产生的场景很多,比如:用户使用返回键退出应用,然后马上又重新启动应用。
先对比一下下三种启动的时间:
可以看到三者的明显的差距。
为了提升用户体验,可以把闪屏页当作一个Fragment嵌套在MainActivity中,这样就可以在进入闪屏时直接预加载主页的View。闪屏页结束后直接remove掉就可以了。
对于启动的优化就是减少耗时操作,总结如下:
或者使用指令:adb shell am start -S -W [packageName]/[packageName.MainActivity],-S是重新启动应用,也可以查看启动时间:
本文主要做对Android中常见的性能优化浅析;如需深入了解性能优化这块,大家可以参考《Android性能优化》这个技术文档,里面记录了大部分的性能优化的方案。
内存优化问题是面试和平时开发中经常可能都会遇到的问题,也比较复杂,本文就常见问题做了简单的整理。Android性能优化有多种方式,在多个方面都有体现: