App耗电及Crash体验优化

我们都知道,对于移动设备,无论是手机还是平板电脑,其续航时间在用户体验中都扮演重要角色,低续航通常会给用户带来不好的体验,甚至让用户产生危机感,不敢长时间持续使用设备,续航时间越久,这种现象就越不明显,用户体验就越好。若要尽量做到这一点,则关键在于降低App的耗电量。
此外,对于App Crash,相信无论是作为用户还是开发者,都是不想看到的。它不可避免地发生,极大程度上影响用户的体验。

电量优化原则

其实,电量优化的原则非常简单——让App少干活,其原因显而易见——干活就会耗电。那就索性少做点事,前提是不要影响到正常的功能。
对于不同类别的App,要达到这样的目的有不同的做法。比如,对于即时消息App,集成厂商的推送服务,而不是总保持长连接或定时心跳,就是一种省电策略。又比如,对于导航App,当用户处于息屏、返回Home或其他无须导航的情况时,可将GPS资源释放,也可以节约电量。诸如此类的做法,其宗旨就是让App少做事。
除了少做事之外,最好还能让App聪明地做事。举个例子,现有一款文字处理App,在电量充足的状态下,App每隔10分钟会在本地进行文档备份以及上传到网络进行文档备份。当电量不足时,不妨仅在本地备份,并适当地提示用户:在省电模式下未进行网络备份。这样的“动态”机制可以在设备不同状态下执行不同的任务,在某些应用场景中非常有效。

Android系统的耗电策略及应对方案

Android操作系统也内置了一些App运行策略,这些策略在一定程度上节约了耗电量。作为开发者,借助相应的API即可轻松适配这些策略。此外,在调试过程中,借助adb命令可模拟多种设备供电状态,以便测试App在不同状态下的运行表现。

系统本身的策略及应对方案

纵观Android操作系统各版本的更新日志,可以发现:对于App的运行策略限制越来越详细,也越来越复杂。了解这些策略的目的主要有两个:第一,明确设备处于不同状态下对App运行的限制,从而定位由此带来的问题:第二,基于这些限制,可以在适当的时机执行恰当的任务。
此外,对于不同的生产厂商,还会有自定义的限制策略,请各位读者特别留意。

  1. Doze模式
    当设备息屏且持续一段时间不再使用时,系统会进入Doze模式,该模式又称为低电耗模式。一旦系统进入Doze模式,App的执行将会受到以下限制:
  • 网络交互会被中断
  • Wakelock失效
  • Alarmmanager会被推迟
  • 账户同步功能失效
  • Jobscheduler关联的任务不会执行

如果我们用横轴表示时间,纵轴表示耗电量,应用Doze模式后,理想状态下的设备耗电情况如下图所示:
在这里插入图片描述
图中绿色部分表示Doze模式,关闭屏幕并渡过几分钟闲置期后,进入该模式。由于大幅度限制了App的运行,耗电量极少。每隔一段时间,系统宣布暂时退出Doze模式,进入短暂的活动期。活动期对应上图中的4个波峰,系统会在这4个短暂的时间段内执行由于Doze模式而延迟的所有活动,在这期间,网络访问会恢复。一旦活动期结束,系统会再次进入Doze模式。另外,随着时间的推移,每次Doze模式的持续时间会变长。
当用户手动点亮屏幕或将设备连接至充电器时,Doze模式将退出,设备恢复活动状态。
如果处理不当,这样的限制可能会造成App运行出现“假卡顿”的现象,即在设备亮屏使用时,App运行稳定;一旦黑屏一段时间后,本该运行的代码逻辑被推迟或取消操作。比如心跳包,在亮屏时可以达到每两分钟一次心跳,而一旦黑屏一段时间后,就有可能会5分钟一次心跳。如果服务器是通过心跳包来确认用户在线状态的话,就很可能造成用户被迫下线的后果。

  1. Doze模式的适配
  • 典型场景一:心跳包
    心跳包的典型场景就是保持设备与服务器的长连接状态。说实话,我不建议在App中使用心跳包,不仅耗电,还耗流量。其实,进到心跳包,往往离不开推送。如果可能,建议读者集成各厂商的推送SDK,彻底和心跳包说再见。大部分情况下,这些厂商自己的推送服务不会应用Doze模式的网络限制策略。也就是说,集成推送服务后,即便我们开发的App被限制了网络,其中的推送服务仍然可以使用。
    如果迫不得已,必须使用心跳包,那么可以借助AlarmManager。但在设备闲置状态下,AlarmManager有可能被推迟到下一个活动期才会被执行,这样还是会造成心跳延迟,如何处理AlarmManager?接着看场景二
  • 典型场景二:AlarmManager
    为了使AlarmManager中的任务可以准时执行,从API Level23起,可以调用setAndAllowWhileIdle()和setExactAndAllowWhileIdle()方法规避延迟的问题。但要特别注意的是,上述两个方法触发的AlarmManager频率不得多于9分钟。为了使Doze模式真正发挥作用,切勿滥用AlarmManager。
  1. App-Standby模式
    当系统认为App在非活动状态时,进入待机状态,即App-Standby状态。
    App-Standby模式和Doze模式最大的区别在于:前者针对某一个App进行限制,后者针对设备中所有的App进行限制。
    App待机状态的进入条件很简单,当用户不再与该App发生交互时,App将在一段时间后转为待机状态。但有几个例外情况:
  • 用户明确启动应用。
  • 应用当前有一个进程在前台运行(作为活动或前台服务,或者正在由其他活动或前台服务使用)。
  • 应用生成用户可在锁定屏幕或通知栏中看到的通知。
  • 应用是正在使用中的设备管理应用(例如设备政策控制器)。虽然设备管理应用通常在后台运行,但永远不会进入应用待机模式,因为它们必须保持可用性,以便随时从服务器接收策略。

和Doze模式类似,当用户和App发生交互,或将设备与充电器连接时,App将由待机模式退出,可以自由地运行。
在android9版本中,对应用待机模式引入了新的规则,即应用待机分类规则。该规则旨在根据App的使用情况和预测应用行为,将其分为优先级不同的5类:即活跃、工作集、常用、极少使用以及从未使用。

  • 活跃:该类App指当前正在与用户发送交互的,或者刚刚使用结束不久的App,这类App的执行不会受限,它是自由的。但要注意,没有主启动Activity以及发送的通知不具备交互属性的App不会出现在活跃类别中。
  • 工作集:该类App包含用户特别经常使用的App,通常每天都会启动一次或多次,这类App的执行会受限,但很少。
  • 常用:该类App包含用户常用但并非每天都在用的App,比如大众点评等每逢休闲时光才会使用的App。这类App的执行受限稍显严格。
  • 极少使用:该类App的使用频率不高,比如旅游类app,该类app每逢旅游才会使用。这类应用的执行限制更多,比如网络交互。
  • 从未使用:app安装后就没有启用过,它的执行限制最严格。
    在这里插入图片描述
    最后,由于待机分类是针对包而言的,如果你的App包含多个包名,那么它们有可能分布在不同的应用待机分类中。
  1. App-Standby模式的适配
    前文提及到,当用户不再与App发生交互时,过一段时间,App将进入待机状态。若要保持App持续活动,则需要结合具体的需求来确定。
    比如,我们正在开发一款导航App,当处于导航状态下,无论用户怎样做,除非退出程序或强行停止,我们都应该持续提供导航服务;再比如,一款音乐播放器,即使用户将App切换到后台,也不能无故停止音乐的播放。对于这类App,我们可适当调整Service级别到Foreground或在通知栏处显示通知。这样可以规避系统误将App转为待机状态。当然,不应利用该途径阻断正常的App状态转换。
    此外,对于应用待机分类,也不应主动干预系统强制某个App的具体分类。对于包含多个包的App,在测试时应尽可能地覆盖各种待机分类组合,以确保App在任何状态下都可能正常运作。
  2. 添加白名单不是万金油
    通常,如果想让系统排除app的耗电优化,就会添加名为REQUEST_IGNORE_BATTERY_OPTIMIZATIONS的权限,并启动ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS的对话框引导用户添加白名单。但即使如此,也无法确保App的执行是完全自由的。
    对于已经添加到白名单的App,其JobScheduler以及AlarmManager在API Level23及以下版本中仍然会被推迟执行。
使用adb模拟设备状态

在API Level23及更高版本的Android操作系统上,我们可以通过adb命令模拟设备电池状态。这样一来,对于App的测试与调试就更加轻松了。
要迫使整个设备进入Doze模式,可在命令行执行:

adb shell dumpsys deviceidle force-idle

反之,退出Doze模式,可执行:

adb shell dumpsys deviceidle unforce

执行以下命令,重新激活设备:

adb shell dumpsys battery reset

要迫使某个App进入App-Standby模式,可在命令行执行:

adb shell dumpsys battery unplug
adb shell am set-inactive true

反之,若要唤醒它,则可执行:

adb shell am set-inactive false
adb shell am get-inactive

要改变某个App的待机分组,可执行:

adb shell am set-standby-bucket [待机分类名]
待机分类名通常为active(活跃)、working_set(工作集)、frequent(常用)、rare(极少使用)中的一个。

若要查看某个App所在的分组,则可执行:

adb shell am get-standby-bucket

或在App的代码中调用:

UsageStatsManager.getAppStandbyBucket()

App Crash体验优化

相信很多读者都有过这样的经历:自己玩着某个程序,突然屏幕一黑,然后出现一个对话框,提示:“很抱歉,应用程序已经停止工作”。
这意味着出现已经崩溃了,用户唯一能做的就是重新运行这个程序,或者干脆不再使用它了,可想而知,我们的目标是:程序发生了异常后,尽量让用户不离开本程序,并尝试恢复运行。因此,我们需要自定义异常处理流程,然后自动重新启动程序,最后恢复崩溃前的现场。
看下去很复杂,其实很容易:核心在于对Application的继承。
在BaseApplication 中创建一个名为 Crashiandler的子类,该类实现
UncaughtExceptionHandier 接口,复写 uncaughtException 方法。如上所述,这里我们需要重启程序,因此名为 CrashHandler 的内部类的实现可参考下例:

class CrashHandler implements UncaughtExceptionHandler{
   @Override
   public void uncaughtException (Thread thread, final Throwable ex)
     ex.printStackIrace():
     Intent restartIntent = new Intent (BaseApplication.this,
SplashScreen.class);
     restartIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
     startActivity(restartIntent),
     android.os.Process.killProcess(android.os.Process.myPid());
}

这里需要注意的是,Intent对象的Flag必须包含FLAG_ACTIVITY_NEW_TASK。最后,在复写的onCreate方法中,指定异常处理的方法:

Thread.setDefaultUncaughtExceptionHandler(crashHandler);
//crashHandler为CrashHandler对象。至此,我们的程序发生崩溃时就不会出现程序崩溃的对话框

最后,根据实际 App 运行状态尝试对崩溃现场的数据进行还原。
这里要特别强调一点:如此重启,若处理不当,则可能会造成更严重的后果——app循环崩溃,这导致整个设备无法使用。比如,当发生崩溃的代码位于 onCreate()方法中时,程序是无法重启成功的。此时,用户别无他法,只能重启设备了。
应对这种情况,可以尝试在要启动的 Activity 的onCreate()方法开头和 onResume()方法未尾添加判断逻辑,其具体思路是添加一个名为 ishouldRestart 的布尔型全局变量,随后检查程序是否是在崩溃后重启的。在onCreate()方法开始处将 ishouldRestart置为false, 表示暂不开启崩溃自动重启逻辑。在onResume()方法的末尾将该变量置为true, 表示重启已经成功,若再次发生崩溃,则自动重后机制仍然生效。与之相配合的,在发生崩溃时,应检查是否可以应用重启机制。若ishouldRestart 变量为 false,则不要运行重启逻辑;否则运行。这样一来,既可以快速还原崩溃前的现场,又可以规避由此带来的副作用。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值