对于应用开发者而言,衡量应用成功最好的指标就是开心的用户,而且是越多越好。达到这一目的的最佳途径就是开发一个好应用,那么什么样的应用才能被称作是 “好” 应用呢?归根结底就是两件事:功能以及应用质量。前者取决于开发者的创造力以及选用的商业模型;而后者则能够被客观测量及改善。
去年谷歌进行的一项内部调查显示 Play Store 中超过 40% 的一星应用存在稳定性问题。另一方面,对于性能卓越的应用,人们打分和评论往往越来越好,这让它们在 Google Play 中的排名上升,下载量也随之增加。不仅如此,用户参与度也更高,而且愿意花更多的时间和金钱在这些应用上。
因此,解决应用稳定性问题能够显著影响到应用成功与否。
通过对应用质量的客观测量,开发者能够轻易发现应用亟待解决的稳定性问题,为此我们在 Google Play Console 添加了一款名为 Android vitals 的新板块。借助 Android vitals,开发者无须添加额外工具代码或者库就能了解应用存在的性能及稳定性问题。当应用在大量设备上运行时,Android vitals 会收集与应用性能相关的匿名数据。通过这种途径获得的信息量是其他方式无法匹及的,即使是硬件实验室测试也不行。
Android vitals 可以向开发者发送以下三种警告:崩溃、应用程序无法响应以及渲染次数。这三种情况都会直接影响到用户体验以及他们对应用的评价。此外,用户可能不会将 “异常耗电事件” 这类不良行为与您的应用直接联系起来。
这篇文章将探讨其中以下两个问题:
1. 过度唤醒:过度唤醒会对电池寿命造成影响,而且在无法及时充电的情况下,可能导致用户无法继续使用设备。此类行为可能会让用户迅速卸载您的应用;
2. 应用程序无法响应 (ANR) 事件:当应用的用户界面卡住时候,此类事件会被触发。在界面冻结时,若您的应用在前台运行,会出现对话框提醒用户 “关闭应用” 或者 “等待响应”。对用户而言,此类行为和应用崩溃一样糟糕。他们可能不会马上卸载您的应用,但是如果 ANR 问题一直不解决,就很有可能会寻找其它替代应用。
过度唤醒
那么,什么是唤醒?什么时候又是唤醒 “过度” 呢?
为了延长电池续航时间,屏幕关闭后,Android 设备会禁用主 CPU 内核,进入深度睡眠模式。除非用户唤醒设备,设备最好可以尽可能长地保持这种状态。不过,在发生某些事件的情况下,还是很有必要唤醒 CPU 并向用户发出警告 —— 比如说,闹钟触发或者收到新消息。开发者可以通过唤醒闹钟 (wakeup alarm) 来处理此类警告,不过也不一定非要这么操作,在下文中会对此稍加解释。到目前为止,唤醒看上去似乎是个不错的东西,让重要事情能引起用户注意,不过要是唤醒太多次就适得其反,电池寿命也会大打折扣。
Android vitals 如何显示过度唤醒
Android vitals 能够帮助开发者了解自己的应用是否存在唤醒次数太多的问题。通过收集有关应用行为的匿名数据,Android vitals 可以显示有多少比例的用户在设备满电之后,每小时经历 10 次以上的设备唤醒。关键就是看有没有红色的图标出现,若图标出现,则说明应用已经越过了不良行为门槛,属于 Google Play 中表现最次的一档应用,而您则须要想办法改善应用行为了。
除了唤醒闹钟,还有别的方法吗?
开发者主要是通过 AlarmManager API 设定 RTC_WAKEUP 或 ELAPSED_REALTIME_WAKEUP 旗标,让应用在特定时间或者在某一时间间隔后唤醒设备。该功能须谨慎对待,仅在没有其它更优的任务调度和通知机制的情况下才可使用。在使用唤醒闹钟的时候,您需要考虑以下几点:
>>若您需要显示信息以响应来自网络的数据,考虑通过使用 Firebase Cloud Messaging 等工具来实现消息推送。利用该机制而不是定期轮询新数据,您的应用会仅在需要时才被唤醒。
>> 如果您无法使用消息推送并依赖定期轮询,考虑使用 JobScheduler 或者 Firebase JobDispatcher (或者使用 SyncManager 来处理账户数据)。它们的 API 等级比 AlarmManager 高,而且在智能任务调度方面具备以下优点:
-- 批量操作:批量操作任务而不是多次唤醒系统进行操作,这使设备能更长时间处于睡眠状态。
-- 标准:您可以明确任务运行须满足的具体标准,如网络可用性或者电池充电状态。设定标准能够避免唤醒设备以及不必要的应用运行。
-- 持续性以及自动退避 —— 继续执行任务 (即使在重启后) 并且在失败的情况能自动重试。
-- 低耗电模式 (doze) 兼容性 —— 仅在低耗电模式或者应用待机模式未设定任何限制的情况下,任务才能运行。
当且仅当消息推送以及任务调度对您的任务不适用时,您才可以利用 AlarmManager 设定唤醒闹钟。换个角度来说就是,仅当您想要在特定时间触发闹钟,不考虑网络以及其它情况,唤醒闹钟才是必要的。
当 Android vitals 显示过度唤醒时,您应采取何种对策?
为了解决过度唤醒问题,您须要确认应用在什么地方设定了唤醒闹钟,然后降低这些闹钟的触发频率。
那么如何查看应用在哪些地方设了唤醒闹钟呢?您可以打开 Android Studio 中的 AlarmManager 类,右击 RTC_WAKEUP 或者 ELAPSED_REALTIME_WAKEUP 域,选择 "Find Usages (查找使用)",然后您就能看到项目中所有使用到此类旗标的事件了。仔细查看每一种事件,然后考虑能否改用更为智能的任务调度机制。
您也可以将 Find Usage (查找使用) 中的范围设定为 “Project and libraries (项目和库)”,查看依赖项是否在使用 AlarmManager API。如果确实在使用,那么您应该考虑使用别的库,或者向依赖项开发人员报告错误。
若您认为使用唤醒闹钟无法避免,那么如果您的闹钟标签满足以下要求,Play Console 可以提供更好的分析数据:
>>在闹钟标签中包含包、类或者方法名称。这也可以帮助您轻松确定在源中的哪些地方设定了闹钟;
>>不要使用 Class#getName() 给闹钟命名,因为 Proguard 会对此产生混淆。请使用硬编码字符串;
>>不要向闹钟标签添加计数器或者其它唯一标识符,因为系统可能会贵去掉这类标签,而且无法将它们计入有效数据内。
应用程序无法响应
那么,什么是应用程序无法响应 (以下简称为ANR)?它又是怎么影响到用户的呢?
对用户而言,ANR 就是指当他们试图与应用进行交互时,但界面卡住的事件。界面卡屏几秒后,会出现对话框让用户选择继续等待或者强行停止应用。
从开发者的角度来看,ANR 则是指应用运行的操作耗时过久,如磁盘或网络 I/O,导致主线程阻塞。主线程 (有时候也被称为 UI 线程) 主要负责响应用户事件以及每秒刷新 60 次屏幕。因此很关键的一点将任何可能延时主线程工作的操作转到后台线程。
Android vitals 如何显示应用程序无法响应?
Android vitals 能收集并利用应用 ANR 事件的匿名数据,提供多个级别的 ANR 具体报告。主界面上概述了您应用中 ARN 活动的概览信息,显示用户至少经历一次 ANR 事件的日对话比重,并且提供前一天以及前 30 天的情况的单独报告。同时也提供了不良行为门槛。
打开详情界面,即 ANR 比率页面,您能够了解不同时间的 ANR 具体比例,以及针对不同应用版本、活动名称、ANR 类别、以及 Android 系统下的 ANR 情况。您可以就 APK 版本代码、支持设备、OS 版本以及时间,筛选查看这些数据。
应用程序无法响应常见原因
如上文所述,当应用进程影响到主线程时,ANR 事件会被触发,而导致这种阻塞现象的原因各有不一,较为常见的有:
>>在主线程上执行磁盘或者网络 I/O。这是迄今为止导致 ANR 的最常见原因。虽然大部分开发者认同不应该在主线程上进行读写磁盘或者网络,但是有时候我们就是忍不住这么做。在理想情况下,从磁盘上读取几个字节的数据并不会引发 ANR,但是这绝对不是什么好主意。如果用户的设备闪存很慢,如果其它同时进行读写的应用已经对设备造成了很大压力,而您的应用还在排队等着运行 “快速” 读取操作, 这样真的不够明智,所以千万别在主线程运行 I/O;
>>在主线程上运行长计算。那么内存计算又是怎么一回事呢?访问时间长并不会对内存造成影响,较小的操作应该也没什么问题。但是如果您开始循环运行复杂计算并且处理大数据集,主线程就很容易发生阻塞了。您可以考虑重新调整百万像素大图像的体积,或者在解析大 HTML 文本块后,再将文本显示到 TextView 中。总的来说,还是让应用在后台运行此类操作比较合适;
>>向主线程另一进程同步调用 binder:与磁盘或网络操作相似,在线程间进行阻塞调用时,程序执行会被转移到您无法控制的地方。如果说其它进程忙碌,该怎么办?如果须要访问磁盘或者网络以响应您的请求,又该怎么办?此外,数据在转移到其它进程前,须要经过打包 (parcel) 与解包 (unparcel) 两个步骤,会消耗不少时间。因此,还是建议从后台线程进行进程间调用;
>>使用同步:即使您将复杂操作转移到后台线程运行,依旧须要与主线程沟通以显示计算结果。多线程编程不容易,并且在使用同步锁的时候,很难保证不出现阻塞执行。在最糟糕的情况下,可能会出现死锁问题,即不同线程相互卡死。最好不要自己设计同步,建议使用专门的解决方案,比如说 Handler,将不可变数据从后台线程传回主线程。
如何检测应用程序无法响应原因
寻找触发 ANR 的原因不容易,我们拿 URL 类举个例子:
1. 您想看到 URL#equals (判断两个 URL 是否相同的方法) 阻塞线程吗?SharedPreferences 又怎么处理呢?
2. 如果您是在后台读取数值的话,您能在前台调用 getSharedPreferences 吗?
这两种情况都很可能导致长时间阻塞操作。幸好我们有 StrictMode,不用再自己瞎猜是什么原因导致 ARN 了。在调试构建的时候,您可以使用这个工具捕捉主线程上的意外磁盘或网络访问。
您可以在应用中使用 StrictMode#setThreadPolicy,自定义检查项,包括磁盘和网络 I/O 以及您通过 StrictMode#noteSlowCall 在应用中触发的慢调用。同时,您也可以自己选择让 StrictMode 通过何种方式告知已检测到阻塞调用:应用崩溃、日志记录还是显示对话框?您可参看 ThreadPolicy.Builder class 获取进一步信息。
一旦您消除主线程上的阻塞调用,请记得再上传应用至 Play Store 前,关闭 StrictMode。
解决过度唤醒以及 ANR 问题能够提升应用质量及稳定性,提高应用评分,获取更多好评,最终增加下载量。使用 Android vitals 让您轻松快速地了解应用中亟待解决的问题。发现并解决代码中的这些问题可能并不容易,但是您可以利用工具和技术有效地完成工作。
点击这里您可查看Android 和 Google Play 相关内容信息