英文原文:Google’s Android Performance Patterns
Google 开发者 YouTube 频道发布了探讨 Android 性能模式的 16 个视频, 列出了开发者在创建应用时容易出现的一些性能问题,同时提供了很多处理建议。本文是对这些问题和建议的总结。
渲染性能 101。这一系列视频的讲师是 Colt McAnlis,据他介绍,不当的渲染是 Android 上大部分性能问题的根源。如果一个 Activity 需要 16 毫秒以上的时间来准备在屏幕上渲染下一帧,系统将放弃这一帧。应用的用户会感觉滑动不流畅,或者变换有延迟,从而导致体验不好。McAnlis 推荐了很多用于检查此类问题的工具:Hierarchy Viewer、Traceview、Profile GPU Rendering、Debug GPU Overdraw 和 GPU View Updates。
建议尽量减少失效(invalidation)和布局(layout)的数量。前者可以用 Developer Option Show GPU View Updates 可视化地观察,而后者可以使用 Hierarchy Viewer 来分析。
过度绘制。过度绘制衡量的是一个特定的像素在一帧中被绘制的次数。当然这个值最好是1, 但有时候并不是这样。Debug GPU Overdraw 这款工具可以显示重绘最多的区域,所以开发者就知道要优化什么了。
VSYNC。开发者应该注意帧率变化,尤其是当帧率从高于屏幕刷新率突然掉到低于屏幕刷新率时。这会导致动画出现延迟,用户很容易感知到。
Profile GPU Rendering。从 Developer Options 设置可以访问这个工具,它能够从屏幕上活动的所有 Android Activity 生成性能图,这些 Activity 通常包括通知和导航条,再就是活动的应用。一帧的时间在 16 毫秒以下为宜。性能图会包含所有和渲染相关的主要活动的计时信息,这些活动包括更新显示列表、执行显示列表和处理 glSwapBuffers。
60 fps。要让人眼对复杂动画有不错的感受,帧率最少要达到 60 fps。保持 60 fps 的帧率并避免变化很重要。
定制视图。有些过度绘制优化,比如不去绘制完全被遮盖的组件,但是当为了创建定制的视图而覆盖 onDraw ()方法时,渲染过程可能会缺少这样的优化。为尽量减少过度绘制,建议使用 canvas.clipRect ()来指定定制绘制的精确区域。此外,应该使用 quickReject ()来确定某个区域是不是在剪掉的矩形之外,如果没有必要,应该避免浪费时间绘制这个区域。
内存搅动。为大量很快就会被丢弃掉的小型对象重复分配内存,会致使垃圾收集器多次介入,如果这些时间正是在 16 毫秒的窗口绘制时间内,可能会导致丢帧。借助 Android Studio Memory Monitor,开发者可以将垃圾收集活动以可视化方式显示出来,并确定是否有过多的垃圾收集。之后可以利用 Allocation Tracker 来确定这些内存对象从何而来。然后修复可能导致问题的相关代码,从而避免某些内存分配,或者是把这些分配移到循环之外。此外,对象不应该在 onDraw ()内分配,因为这个方法 1 秒会调用很多次。如果应用确实需要大量很快就要丢弃的对象,建议使用对象池,一次创建,多次复用,从而避免垃圾收集。
内存泄漏。内存泄漏使垃圾收集消耗的时间变长,这又会影响帧率。为确保在用户离开某个 Android Activity 之后,不会出现内存泄漏,McAnlis 建议创建一个不需要消耗内存,或者只需要消耗极少内存的空 Activity,转到这个空的 Activity 并强制执行一次垃圾收集。建议在转移之前和之后,使用 Android Studio 中的堆和分配跟踪工具来确定这个 Activity 是否有内存泄漏。
电量消耗。根据普渡大学和微软的一篇研究论文(PDF),大约 70% 的电量是被第三方广告模块消耗的,它们会执行数据分析、位置发现和广告下载等操作。只有 30% 的电量是应用中实际活动消耗的。Google 建议开发者仔细处理电量消耗问题,因为这是用户最关心的。最重要的建议是,除非绝对必要,不要将设备从睡眠中唤醒。如果必须唤醒,建议使用 WakeLock,并设置一个超时时间,以确保如果应用出现问题,设备可以回到睡眠状态。另一个思路是使用 AlarmManager.setInexactRepeating () ,将唤醒操作和另一个活动结合起来,从而节省电量。
可以将某些 CPU 密集型操作推迟到连上充电器并连接到 WiFi 时,或者将多个工作组合到一次唤醒操作中。这可以借助 JobScheduler API,它支持将某些工作延迟一段时间。还有一个建议,要小心地使用网络连接,因为在发送完一个网络请求和接收到一个响应之后,设备要保持至少 5 秒钟的唤醒状态,以备从服务器传过来的另一个数据包。
McAnlis 还建议利用 SettingsBattery 和 Battery Historian 工具提供的电量消耗详情来监控应用随时间变化的耗电情况。