资源类性能分为:磁盘、CPU和内存,以及与环境密切相关的网络和因为移动网络而显得很重要的电池(耗电)。
1、磁盘
1.1 发现定位工具:Strict Mode 和 Systrace。
对于Strict Mode 的原理,主要是在文件操作(BlockGuardOs.java)、数据库操作(SQLiteConnection.java)和 SharePreferences操作(SharePreferencesImpl.java)的接口中插入检查的代码。
1.2 具体优化点
- 使用缓存,避免重复多次读写某个文件。(比如多次重复读写 /proc/cpuinfo)
每次打开、关闭或者读/写文件,操作系统都需要从用户态到内核态的切换,这种切换本身是很耗时的。 - 延迟写入,对于 SharePreferences 的 commit() ,一个方法中只需保留最后一个。
每调用一次 commit(),都会对应一次文件的打开和关闭。 - 在子线程中读写文件
- 对于读写文件,使用缓存输入输出流
比如对于 ObjectOutputStream 在序列化磁盘时,在保存对象的时候,每个数据成员会带来一次 I/O 操作,导致 I/O 次数巨多。这时可以使用 BufferedOutputStream 来包装一下。 - 合理设置 Buffer 的大小
Buffer 设置太小会导致读写次数太多,Buffer至少应为 4k,Java 默认是 8k。 - 文件解/压缩效率
解压磁盘上的文件,建议使用 ZipFile,效率较 ZipInputStream 提升 15% - 27%。
如果文件不在磁盘上(如网络)或者顺序解压一小部分文件,或者zip文件目录遭到损坏,使用 ZipInputStream。
ZIP压缩大量小文件时使用 ZipInputStream。 - 避免重复多次打开数据库,使用缓存数据库连接。(打开数据库比较耗时)
- 减少使用 AUTOINCREMENT,使用 AUTOINCREMENT 会增加 INSERT 耗时1倍以上。
- 对于 Bitmap 解码,使用 decodeStream 代替 decodeFile;使用 decodeResourceStream 代替 decodeResource。可以提高效率。
2、内存
2.1 原理
- 虚拟机的堆内存最大值
在开发中,要注意App占用的内存大小。Android系统给堆(Heap)设置了一个最大值,可通过 runtime.getruntime().maxmemory() 获取。 - Low Memory Killer 机制
当手机剩余内存低于内存的警戒线阈值时,就会触发 LMK 机制杀掉 APP。 - GC
没有被GC Root间接或直接引用的对象的内存机会被回收。
需要注意 GC for Alloc,这种情况是在内存不足以分配给新的对象时触发。它stop the world 的时间因为GC无法并发而变得更长。
还要注意内存抖动问题。 - 注意 Activity 泄漏
Activity 对象会间接或直接引用 View、Bitmap等,所以一旦无法释放,会占用大量内存。 图片缓存
官方建议使用 Lru 算法来做图片缓存。
缓存方案: LruCache,DiskLruCache 和 BlobCache。内存问题主要包括:
1、常驻问题(主要是图片缓存)
2、泄漏问题
3、OOM
4、GC问题(GC for alloc,内存抖动)后果可能或导致 App Crash、闪退、后台被杀、卡顿,严重就导致 OOM。
2.2 工具
工具 | 问题 | 能力 |
---|---|---|
top / procrank | 内存占用过大,泄漏 | 发现 |
memory info | 泄漏 | 发现 + 初步定为 |
Strick Mode | Activity 泄漏 | 自动发现 + 定位 |
Leak Canary | Activity 泄漏 | 自动发现 + 定位 |
Systrace | GC导致的卡顿 | 发现 |
Allocation Tracker | 申请内存过大;辅助GC log发现的问题;内存抖动 | 发现 + 定位 |
MAT | 内存泄漏 | 发现 + 定位 |
GC log
在 logcat 中可以看到 gc 日志,对于log中 gc 的原因主要有如下几种:
- GC_EXPLICIT:通过 Runtime.gs() 与 VMRuntime.gc(),SIGUSR1 触发产生的 GC。
- GC_FOR_ALLOC:没有足够内存空间给予即将分配的内存,这时会触发。这种GC不是并发GC,对卡顿影响更大。
- GC_FOR_CONCUREENT:当超过堆占用阈值时自动触发,常见、健康GC。
- GC_BEFORE_OOM:触发 OOM 之前触发 GC。不能并发,不能局部GC,所以耗时长,容易卡顿。
- GC_HPROF_DUMP_HEAP:在 dump 内存之前触发的 GC。不能并发,不能局部GC,所以耗时长,容易卡顿。
2.3 具体优化点
- 非静态内部类造成内存泄漏。(内部类持有外部类的引用)
- Activity 对象被其他对象直接引用导致回收不掉,造成泄漏。
- Activity 的 Context 被引用造成泄漏。
- Thread 和 Runnabl 没有执行或没有执行完造成泄漏。
- 定时器 Timer 没有取消导致的内存泄漏。
- Handler 和 调用view.postDelayed() 方法后没有移除掉没有执行的Message造成的泄漏。
3、CPU
3.1 工具
- adb shell top
- adb shell dumpsys cpuinfo
- Systrace
- Traceview
3.2 具体优化点
- 当App退到后台时,应该停止掉正在执行的不需要的任务。
- 优化算法。
- 使用 renderscript 来减少图像处理的 CPU 消耗。
4、电池
4.1 原理
Android 官方建议厂商通过 PowerMonitor 之类的工具来测试每个硬件模块的耗电情况,并配置好 power_profile.xml 文件。power_profile.xml 是一个为了让Android系统能通过硬件调用频率和强度来计算耗电的配置。
从文件的内容可以看出耗电的硬件有:CPU、Wi-Fi、Radio(数据网络)、Senor(感应器)、BlueTooth(蓝牙)、Screen(屏幕)、GPS。还有不属于硬件模块的视频和音频。
adb查看命令:adb shell dumps power
4.2 具体优化点
- 对于有些功能,添加黑屏判断。比如有些功能在黑屏状态下是没有意义的情况。
- 黑屏状态或者在后台时,及时释放动画。
注意使用 SurfaceView 自定义的动画,SurfaceView 是新开了一个线程用于绘制。 - 多个 AlarmManager 定时相近任务进行合并。
- 注意 WakeLock 的及时释放。
注意间接调用:Mediaserver 是系统服务,是媒体功能的核心,音视频的播放、照相、摄像、录音等功能都需要调用到Mediaserver 实现各种媒体相关的功能。当App调用Mediaserver的某些功能时,例如音视频录制,Mediaserver 会申请WakeLock。比如 AudioRecord 会申请一个WakeLock 来避免手机进入休眠状态影响音频的录制,当不需要时,应调用 release() 释放相应资源。 - 注意 WakeLock 的计数机制。(不同厂商可能不一样,有修改)
WakeLock 的 setReferenceCounted 接口用来设置计数机制,true为计数,false为不计数。计数即是每一个 acquire 必须对应一个 release;不计数则是无论有多少个 acquire,一个 release 就可以释放。
参考:腾讯SNG专项测试团队《Android 移动性能实战》