1 内存优化

1.1 内存泄漏

Java虚拟机中的对象

内存泄漏

1.2 内存溢出(OOM Out Of Memory)

内存泄漏一般导致应用卡顿,极端情况会导致 OOM,OOM 的原因是因为超过内存的阈值。原因主要有两方面:

  • 内存泄漏,导致无法及时释放导致 OOM;
  • 一些逻辑消耗了大量内存,无法及时释放或者超过导致 OOM;

OOM

能够消耗大量内存的,绝大多数是因为图片加载。这是 OOM 出现最频繁的地方。图片加载,一个是控制每次加载的数量,二是保证每次滑动的时候不进行加载,滑动完进行加载。一般情况使用先进后出,而不是先进先出。 不过一般我们图片加载都是使用 fresco 或者 Glide 等开源库。

下面是导致内存溢出的两种情况:

内存溢出

通过命令行查看内存消耗情况(adb shell dumpsys meminfo 包名 -d):

内存使用情况

2 UI 优化

对于大多数的手机的屏幕的刷新频率是 60hz,也就是如果在 1000/60=16.67ms 内没有把这一帧的任务执行完毕,就会发生丢帧现象,丢帧是造成界面卡顿的直接原因。

渲染操作通常依赖于两个核心组件:CPU 和 GPU。CPU 负责包括 measure、layout 等计算操作,GPU 负责 Rasterization(珊格化操作),珊格化就是将矢量图转换为位图的过程。

位图:也称为点阵图,是由像素点组成的图像,每个像素都包含了颜色和位置信息。放大位图时,可以看到构成整体图像的无数单个像素点。

矢量图:面对对象的图形或者绘制图像,矢量文件中的图形元素称为对象。每个对象都是一个自成一体的实体,具有颜色、形状、大小和屏幕位置等属性。

手机页面上按照一个个的像素来显示的,比如将一个 Button 拆分成一个个的像素显示到手机屏幕上。

UI 渲染的目的就是减轻 CPU、GPU 的压力,除去不必要的操作,保证每帧能在 16ms 内处理完所有的的计算、绘制、渲染等操作,使 UI 能够顺滑、流畅的显示出来。

UI 优化主要包括布局优化以及 View 的绘制优化。有时候我们打开某个软件,会出现卡顿的情况,这就是 UI 的问题。什么情况会导致卡顿呢?一般是如下几种情况:

  • 在 UI 线程中做耗时操作,导致 UI 线程卡顿;
  • 布局 Layout 过于复杂,无法在 16ms 内完成渲染;
  • 同一时间动画执行的次数过多,导致 CPU 或 GPU 负载过重;
  • View 过度绘制,导致某些像素在同一帧时间内被绘制多次,从而使 CPU 或 GPU 负载过重;
  • View 频繁的触发 measure、layout,导致 measure、layout 累计耗时过多及整个 View 频繁的重新渲染;
  • 内存频繁触发 GC 过多(同一帧中频繁创建内存),导致暂时阻塞渲染操作;
  • 冗余资源及逻辑等导致加载和执行缓慢;
  • ANR;

可以看见,上面这些导致卡顿的原因都是我们平时开发中非常常见的。有些运行起来还可以的程序,一旦进行一些瞬时测试或压力测试,就会发现很多性能问题。

2.1 布局优化
2.1.1 GPU 绘制

对于 UI 性能的优化还可以通过开发者选项中的 GPU 过度绘制工具来进行分析。 在设置 -> 开发者选项 -> 调试 GPU 过度绘制(不同设备可能位置或者叫法不同)中打开调试后可以看见如下图(对 settings 当前界面过度绘制进行分析):

GPU绘制

以下说明:
Overdraw

蓝色(1x 过度绘制),淡绿(2x 过度绘制),淡红(3x 过度绘制),深红(4x 过度绘制)代表了 4 种不同程度的 Overdraw 情况,我们的目标就是尽量减少红色 Overdraw,看到更多的蓝色区域。

UI 渲染优化的第一步就是找到 Overdraw(过度绘制),即屏幕上的某个像素在同一帧的时间内被绘制了多次,从而增加了 CPU、GPU 的压力。

Overdraw 有时候是因为 UI 布局存在大量重叠的部分,还有的时候是因为非必须的重叠背景。例如某个 Activity 有一个背景,然后里面的 Layout 又有自己的背景,同时子 View 又分别有自己的背景。仅仅是通过移除非必须的背景图片,这就能够减少大量的红色 Overdraw 区域,增加蓝色区域的占比。这一措施能够显著提升程序性能。

如果布局中既能采用 RealtiveLayout 和 LinearLayout,那么直接使用 LinearLayout,因为 Relativelayout 的布局比较复杂,绘制的时候需要花费更多的 CPU 时间。如果需要多个 LinearLayout 或者 Framelayout 嵌套,那么可采用 Relativelayout。因为多层嵌套导致布局的绘制有大部分是重复的,这会减少程序的性能。

2.1.2 GPU 呈现模式分析

设置–>开发者选项–>GPU呈现模式分析–>在屏幕上显示为条形图,如图所示:

GPU呈模式分析

随着界面的刷新,界面上会以实时柱状图来显示每帧的渲染时间,柱状图越高表示渲染时间越长,每个柱状图偏上都有一根代表 16ms 基准的绿色横线,每一条竖着的柱状线都包含三部分(蓝色代表测量绘制 Display List 的时间,红色代表 OpenGL 渲染 Display List 所需要的时间,黄色代表 CPU 等待 GPU 处理的时间),只要我们每一帧的总时间低于基准线就不会发生 UI 卡顿问题(个别超出基准线其实也不算什么问题的)。

2.2 绘制优化

绘制优化主要是指 View.onDraw 方法需要避免执行大量的操作:

  • onDraw 方法不需要创建新的局部对象,这是因为 onDraw 方法是实时执行的,产生大量的临时对象,导致占用了更多内存,并且使系统不断的 GC,降低了执行效率;
  • onDraw 方法不需要执行耗时操作,在 onDraw 方法里少使用循环,因为循环会占用 CPU 的时间。导致绘制不流畅,卡顿等等。 Google官方指出,View 的绘制帧率稳定在 60dps,这要求每帧的绘制时间不超过 16ms(1000/60)。虽然很难保证,但我们需要尽可能的降低;

60dps 是目前最合适的图像显示速度,也是绝大部分Android 设备设置的调试频率,如果在 16ms 内顺利完成界面刷新操作可以展示出流畅的画面,而由于任何原因导致接收到 VSYNC 信号的时候无法完成本次刷新操作,就会产生掉帧的现象,刷新帧率自然也就跟着下降(假定刷新帧率由正常的 60fps 降到 30fps,用户就会明显感知到卡顿)。

3 速度优化

3.1 ANR 问题(application not responding)

Android 官方规定:Activity 如果在 5s 内无响应事件(屏幕触摸事件或者键盘输入事件);BroadcastReceiver 如果在 10s 内无法处理完成;Service 如果 20s 内无法处理完成。这三种情况会导致 ANR。

上面说的三种导致 ANR 的情况,绝大多数就是因为线程阻塞导致的。 那么应该如何处理呢?Android 系统为我们提供了若干组工具类来解决此问题:

  • Asynctask:为 UI 线程与工作线程之间进行快速处理的切换提供一种简单便捷的机制。适用于当下立即需要启动,但是异步执行的生命周期短暂的场景;
  • HandlerThread:为某些回调方法或者等待某些执行任务的执行设置一个专属的线程,并提供线程任务的调度机制;
  • ThreadPool:把任务分解成不同的单元,分发到各个不同的线程上,并发处理;
  • IntentService:适合执行由 UI 触发的后台任务。并可以把这些任务执行的情况通过一定的机制反馈给 UI;

线程池

3.2 图片处理
  • 使用 WebP 格式:同样的照片,采用 WebP 格式可大幅节省流量,相对于 JPG 格式的图片,流量能节省将近 25% 到 35 %;相对于 PNG 格式的图片,流量可以节省将近 80%。最重要的是使用 WebP 之后图片质量也没有改变;
  • 使用缩略图;
3.3 网络请求处理
  • 对服务端返回数据进行缓存,设定有效时间,在有效时间之内不走网络请求,减少流量消耗;
  • 在某些情况,尽量少使用 GPS 定位,如果条件允许,尽可能使用网络定位;
  • 下载、上传,尽可能使用断点续传;

4 启动优化

Android 应用的启动方式分为三种:冷启动、暖启动、热启动,不同的启动方式决定了应用 UI 对用户可见所需要花费的时间长短,冷启动消耗的时间最长。 基于冷启动方式的优化工作也是最考验产品用户体验的地方。

4.1 冷启动

应用启动时,后台没有该应用的进程,这样的启动就是冷启动。例如,第一次启动 APP,又或者说杀死进程后第一次启动。那么对比其他两种方式。冷启动自然是耗时最久的。

应用发生冷启动时,系统一定会执行下面的三个任务:

  • 开始加载并启动应用;
  • 应用启动后,显示一个空白的启动窗口(启动闪屏页);
  • 创建应用信息;

那么创建应用信息,系统就需要做:

  • 创建 Application
  • 启动主线程
  • 创建应用入口的 Activity
  • 填充加载布局 View
  • 执行 View 的绘制过程(measure —> layout —> draw)

这其中有两个 creation 工作,分别为 Application 和 Activity creation。它们均在 View 绘制展示之前。所以,在应用自定义的 Application 类和第一个 Activity 类中,onCreate() 方法做的事情越多,冷启动消耗的时间越长。

4.2 暖启动

当应用中的 Activities 被销毁,但在内存中常驻时,应用的启动方式就会变为暖启动。相比冷启动,暖启动过程减少了对象初始化、布局加载等工作,启动时间更短。但启动时,系统依然会展示闪屏页,直到第一个 Activity 的内容呈现为止。

4.3 热启动

相比暖启动,热启动时应用做的工作更少,启动时间更短。热启动产生的场景很多,比如:用户使用返回键退出应用,然后马上又重新启动应用

4.4 如何优化

先对比一下下三种启动的时间:

启动方式
可以看到三者的明显的差距。

为了提升用户体验,可以把闪屏页当作一个 Fragment 嵌套在 MainActivity 中,这样就可以在进入闪屏时直接预加载主页的 View。闪屏页结束后直接 remove 掉就可以了。

对于启动的优化就是减少耗时操作,总结如下:

  • 主线程中涉及到 Shareperference 能否在非 UI 线程执行;
  • Application 的创建过程中尽量少的进行耗时操作;
  • 减少布局的层次,并且生命周期回调的方法中尽量减少耗时的操作;

或者使用指令:adb shell am start -S -W [packageName]/[packageName.MainActivity],-S 是重新启动应用,也可以查看启动时间:

adb

5 电量优化

音乐和视频是耗电量最大的。对于电量优化:

  • 需要进行网络请求时,我们需先判断网络当前的状态;
  • 在多网络请求的情况下,最好进行批量处理,尽量避免频繁的间隔网络请求;
  • 在同时有 wifi 和移动数据的情况下,我们应该直接屏蔽移动数据的网络请求,只有当 wifi 断开时在调用,因为,wifi 请求的耗电量远比移动数据的耗电量低的低;
  • 后台任务要尽可能少的唤醒 CPU,比如说,锁屏时,QQ 的消息提示行就是唤醒了 CPU,但是它的提示只有在你打开锁屏或者进行充电时才会进行提示;

参考

https://zhuanlan.zhihu.com/p/434867334
https://www.kancloud.cn/kancloud/android-performance/53233
Android App启动时间测量

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐