一、简介
性能调优是开发中少不了的一个过程,同时也是一名优秀的程序员需要掌握的基本技能。下面我们来看一下在 Android 中可以从哪些方面来进行性能优化吧。
二、内存优化
内存是很重要的一个模块了,所以我也将其放在了第一位来说明。那么如何来优化我们应用的内存呢?
当应用内存不足时,将发生内存溢出。内存溢出会直接导致应用 crash。尽量减少应用的内存使用将很好的避免发生内存溢出。我们可以从如下方面优化内存:
1. 内存泄漏
首先我们应该确保我们的应用不存在内存泄漏的情况。内存泄漏是指长生命周期对象持有短生命周期对象的引用,而导致短生命周期对象无法及时释放的问题,例如一个静态变量持有某个 Activity 的引用而导致该 Activity 退出时无法被回收。
Android 中常见的内存泄漏场景有:
- Handler
我们可以使用静态内部类的 Handler,并让 Handler 只持有外层 Activity 的弱引用;在 Activity 不再需要时,可以手动清空对应 Handler 中的所有消息。 - 打开的资源未关闭
例如文件、数据库等,打开后都是需要关闭的。这类问题通常比较好排查,使用一些代码检查工具(如 lint 等)都可以帮助你找到未关闭的资源。 - 注册的回调没有注销
一些回调方法,通常命名如 registerCallback 对应有 unregisterCallback,addCallback 对应有 removeCallback。这些回调都是需要我们成对使用的,否则很可能产生内存泄漏。 - 单例模式持有的引用
通常单例对象都是 static 的,其生命周期都很长。当单例对象持有某个短生命周期对象的引用时,如某个 Activity,将导致该 Activity 无法被系统回收。
我们应该手动清除 Activity 的引用,或者是当单例中需要上下文时,直接使用 Application 作为其上下文。 - 静态成员持有的引用
该引用对象如果不再需要使用,应该手动将引用置为 null。 - 非静态内部类的静态实例
非静态内部类对象会持有外部类的引用,如果该非静态内部类对象是静态的,也将导致外部类对象无法被回收。
2. 图片
- 图片压缩:对于大图片,可以压缩后加载(图片压缩方法可自行百度)。
- 图片使用完后,及时释放。
- 图片素材放在合适的目录下,如 xxhdpi、xxxhdpi 等,系统加载不同目录下的图片资源时,会根据手机 dpi 对其进行一定的缩放。一张较大的图片放在较低的 dpi 目录下容易导致系统加载时又进行放大而带来很大的内存消耗。
- 图片缓存:当图片众多时,可以使用 池 来管理,并设置恰当的池大小(通常图片框架中都有相似实现了)。
- 善用开源的图片框架吧。
3. 使用合适和数据结构
选用合适的数据结构,往往不仅可以减少内存的使用,也可以加快运算速度。你需要了解 栈、队列、数组、链表、树、哈希表 等常见数据结构,并根据实际场景选用合适的数据结构。
这里介绍两个 Android 推荐使用的,类似 键值对 功能的类:SparseArray
和 ArrayMap
。相比 HashMap
,它们占用更少的内存,且功能相似,虽然性能上略逊一筹,但在 1000 以下的数量级上,性能上的差异基本可以忽略,但内存占用将少很多。
4. 其它优化
- 使用 int 代替枚举
枚举本质还是对象,比 int 多使用两倍左右的内存。不过枚举也有其优势,我们可以考虑使用 int 来代替枚举而减少内存。 - 使用对象池
对于需要频繁创建和释放的对象,我们可以考虑使用对象池来管理,通过重用对象来避免反复的创建释放。 - ListView 的复用
这个老生常谈了,通过 ViewHolder 重用布局。或者使用RecyclerView
。
三、布局优化
1. 尽量选择使用简单的布局
简单的控件加载起来更快,当简单的控件可以满足需求时,应尽量考虑使用简单的控件。
常用布局控件复杂度:FrameLayout
< LinearLayout
< RelativeLayout
。
2. 减少布局的嵌套层级
当布局的嵌套层级增大时,将大大减慢 xml 的解析速度,而影响到页面的显示。所以我们应当保证布局的嵌套层级尽可能的低。
3. 其它优化
- 使用
include
标签重用布局。 - 合理的使用
merge
标签,可优化布局的嵌套层级。 - 使用
style
标签,抽离公共的风格,可减少代码量,也更易维护。 - 使用
ViewStub
,对于某些不常用的控件,可在需要的时候再进行加载。
四、UI 卡顿优化
UI 卡顿将直接影响到用户的使用体验,是很重要的一个优化环节。
1. ANR
ANR 即页面无响应,产生原因有:Activity 中超过 5s 无响应;BroadcastReceiver 中,前台广播超过 10s,后台广播超过 60s;Service 20s 未完成启动;ContentProvider 的 publish 在 10s 没进行完等。
解决方法:避免在 UI 线程中进行耗时的操作,注意四大组件的大多数回调均在主线程中。
2. View 绘制慢
Android 的渲染需要在 16ms 内完成,否则会产生卡顿的现象。
避免在 onDraw()
方法中进行任何耗时操作,包括频繁创建局部对象(最好不要在该方法中创建局部对象)等。
避免频繁触发 view 的 layout
方法,因为会重新测量和绘制。
3. 动画
避免在同一时间执行过多的动画,导致 CPU/GPU 负载过大。
实现动画时,可以考虑一些开源的优秀的动画框架。
尽量使用硬件加速来完成动画。
4. GC(垃圾回收)
由于 GC 时将挂起其它所有线程,所以频繁的 GC 将带来卡顿的现象。
避免频繁创建释放对象,避免内存负荷大将减少 GC 的频率。
五、其它优化
1. 线程池
开启一个新的线程花销是很大的,如果应用需要经常创建新的线程,就考虑使用线程池吧。通过重用旧的线程对象减少创建新线程的开销。
2. 网络
一个页面的数据尽可能的放在同一个接口里,从而减少网络访问的次数。
大量的数据可以使用分页加载、缓存等。
3. IO
尽量减少 IO 的访问次数,例如读取一个文件时,一个字节一个字节的读取的话将频繁的访问 IO,我们可以一次性读取更多的字节而减少 IO 访问次数。
同理,数据库也是这样。
4. 代码量
尽量精简我们的代码,移除无用的资源和代码,选择使用更轻量级的依赖库。这也将大大减小我们 apk 的体积。
5. 运算
例如进行乘 2、除 2 等操作时,使用位运算(左移、右移)比乘除运算效率将好很多。尤其是这个运算发生在某个较大的循环体内时。
对于一些操作,我们可以选择使用 预处理 和 延时计算 的策略。
6. 异常、锁
try - catch
和加锁的操作都是较重量级的,我们可以尽量不使用它们。例如一些线程同步的场景中选择使用 原子类 或 volatile 关键字代替锁。
如果需要使用的场景下,我们也应该尽量保证其粒度足够小,即其包含的语句尽量少。
7. 更多
更多性能优化的方法,大家也可以留言。