定时器AlarmManager常常用于需要周期性处理的场合,比如闹钟提醒、任务轮询等等。并且定时器来源于系统服务,即使App已经不在运行了,也能收到定时器发出的广播而被唤醒。似此回光返照的神技,便遭到开发者的滥用,造成用户手机充斥着各种杀不光进程,就算通过手机安全工具一再地清理内存,只要定时设定的时刻到达,刚杀掉的流氓App就会死灰复燃。长此以往,手机的运行速度越来越慢,内存也越来越不够用了,更糟糕的是,电量消耗地越来越快。

Android手机越用越慢的毛病老大不掉,为此每次系统版本升级,Android都力图在稳定性、安全性上有所改善。针对定时器AlarmManager的滥用问题,Android从4.4开始,修改了setRepeating方法的运行规则。原本该方法可指定每隔固定时间就发送定时广播,但在Android4.4之后,操作系统为了节能省电,将会自动调整定时器唤醒的时间。比如原来调用setRepeating方法设定了每隔10秒发送广播,但App在实际运行过程中,很可能过了好几分钟才发送一次广播,这意味着该方法将不再保证每次工作都在开发者设置的时间开始。

正如博文《 Android开发笔记(七十五)内存泄漏的处理》描述的那样,当时为了演示定时器发生内存泄漏的场景,并没有直接调用setRepeating方法,而是接力调用set方法。App每次收到定时广播之后,还得重新开始下一次的定时任务,如此方可兼容Android4.4之后的持续定时功能。下面是将setRepeating方法改为使用set方法实现的代码例子:

    private String ALARM_EVENT = "com.example.performance.alarm";private static AlarmManager mAlarmManager;private static PendingIntent pIntent;private static int mDelay = 3000;// 设置定时任务,注意setRepeating的时间间隔并不可靠,只能调用set方法间接实现定时private void setAlarm() {Intent intent = new Intent(ALARM_EVENT);pIntent = PendingIntent.getBroadcast(this, 0, intent,PendingIntent.FLAG_UPDATE_CURRENT);mAlarmManager = (AlarmManager) getSystemService(ALARM_SERVICE);// 在API 19(即Android4.4)之后,操作系统为了节能省电,会调整alarm唤醒的时间,// 所以setRepeating方法不保证每次工作都在指定的时间开始,// 此时需要先注销原闹钟,再调用set方法开启新闹钟。// mAlarmManager.setRepeating(AlarmManager.RTC_WAKEUP,//          System.currentTimeMillis(), mDelay, pIntent);mAlarmManager.set(AlarmManager.RTC_WAKEUP,System.currentTimeMillis()+mDelay, pIntent);}// 定义一个定时广播的接收器public static class AlarmReceiver extends BroadcastReceiver {@Overridepublic void onReceive(Context context, Intent intent) {if (intent != null) {if (tv_alarm != null) {mDesc = String.format("%s\n%s 闹钟时间到达", mDesc, DateUtil.getNowTime());tv_alarm.setText(mDesc);// 设置下一次的定时任务repeatAlarm();}}}}// 每次时刻到达,都重新设置下一次的定时任务,从而间接实现了持续唤醒的功能private static void repeatAlarm() {// 取消原有的定时任务mAlarmManager.cancel(pIntent);// 开启新的定时任务mAlarmManager.set(AlarmManager.RTC_WAKEUP,System.currentTimeMillis()+mDelay, pIntent);}

上面瞒天过海的办法看似完美规避了Android4.4的运行规则,可惜广大开发者还没来得及沾沾自喜,Android6.0又推出了更加严格的休眠模式。所谓休眠模式,即是当手机屏幕关闭的时候(又称熄屏、暗屏),系统就会自动开启休眠模式,这样原本正在运行的App将进入挂起模式,不能再进行访问网络等常用操作。当然为了保证App不被完全挂死,系统也会定期退出休眠模式,好比青蛙从冬眠之中苏醒过来,在苏醒期间,系统允许挂起的App重新恢复运行,继续先前设定好的任务。可是这个苏醒期是短暂的(通常只有几秒),一旦苏醒期结束,系统又重新进入休眠模式,于是那些App再次挂起,等待下次苏醒期的到来,如此往复。当然,只要手机恢复亮屏,比如用户按下电源键、用户给手机插上电源、手机接到来电等等,系统便自动退出休眠模式,所有挂起的App都会恢复正常运转。

手机在休眠期间,之前通过定时器的set方法设定好的定时任务,即使定时的时刻到达,也要等到苏醒期间才会得到执行。如果一定要在休眠期唤醒闹钟,就得调用setAndAllowWhileIdle代替set方法,或者调用setExactAndAllowWhileIdle代替setExact方法。其中setAndAllowWhileIdle与setExactAndAllowWhileIdle这两个方法是Android从6.0开始新增的定时方法,字面意思是即使正在休眠、也要执行定时任务。然而休眠模式的本意是挂起包括定时任务在内的App事务,现在却提供setAndAllowWhileIdle方法留下了后门,为开发者的鸡鸣狗盗之事大开方便,如此规定岂不是贻笑大方?

这光景,简直是活脱脱的一出Android版本的自相矛盾,话说Android设计师当街叫卖Android的安全盾,号称这面盾很牢固、没有矛可以刺穿;前来踢馆的开发者拿着一把Android的setRepeating矛,说道这把矛可以破了那面盾。设计师眼看不妙,赶忙拿起另一面名叫Android4.4的安全盾,又称你的setRepeating矛不行了;开发者精明得很,随身抄着一把Android的set矛,又道这把矛可以破了那面Android4.4的盾。设计师火冒三丈,心想岂能甘拜下风,于是拿出一面Android6.0的休眠盾,声称有此盾护身不怕set矛;谁料道高一尺、魔高一丈,开发者夺过一把Android出产的setAndAllowWhileIdle矛,依旧能刺开Android6.0休眠盾。结果Android设计师大汗淋漓,却不肯认输,嘴里碎碎念:“此山是我开,此树是我栽,要从此路过,留下买路财。罢了罢了,甭管你的矛有多锋利,反正我规定休眠盾至少能抗住九分钟。”这里的九分钟参见Android官方说明:Neither setAndAllowWhileIdle() nor setExactAndAllowWhileIdle() can fire alarms more than once per 9 minutes, per app,意思是不管是setAndAllowWhileIdle还是setExactAndAllowWhileIdle,在休眠期内每个App每隔9分钟最多只能唤醒一次闹钟。

一方面要照顾用户的手机省电需求,另一方面要考虑开发者的业务实现,开发Android的谷歌公司真是煞费苦心,只可惜鱼与熊掌不可兼得呀。我们作为开发者,要让定时器适配Android6.0的休眠模式倒也不难,只需把下面这行的set方法代码:

        mAlarmManager.set(AlarmManager.RTC_WAKEUP,System.currentTimeMillis()+mDelay, pIntent);

改成下面兼容6.0的代码就好了:

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {mAlarmManager.setAndAllowWhileIdle(AlarmManager.RTC_WAKEUP,System.currentTimeMillis()+mDelay, pIntent);} else {mAlarmManager.set(AlarmManager.RTC_WAKEUP,System.currentTimeMillis()+mDelay, pIntent);}

其实就是判断当前系统版本,对于Android6.0及以上版本,使用setAndAllowWhileIdle方法替换set方法即可。

点此查看Android开发笔记的完整目录

Android开发笔记(一百六十)休眠模式下的定时器控制相关推荐

  1. Android开发笔记(六十六)自定义对话框

    AlertDialog Android中最常用的对话框是AlertDialog,它可以完成常见的交互操作,如提示.确认.选择等等,然后就是进度对话框ProgressDialog(参见< Andr ...

  2. Android开发笔记(六十二)HTTP数据格式的解析

    json解析 android有两种主流的json解析方案,一种是sdk自带的由Google提供的json(包名前缀为org.json),另一种是Alibaba提供的第三方jar包fastjson(包名 ...

  3. Android开发笔记(六十八)工程库打包

    写好一个Android模块,比如说一个自定义控件或某个功能的sdk,然后开放出来给别人使用,就得通过某种方式把源码提供给对方.常见的打包方式有: 一.直接给源码,由开发者把代码加入到自己的工程中 该方 ...

  4. Android开发笔记(六十五)多样的菜单

    菜单Menu Android的菜单分为两类:选项菜单和上下文菜单,默认使用选项菜单.菜单的布局文件存放在res/menu目录下,使用ADT新建一个Android工程,首页代码MainActivity中 ...

  5. Android开发笔记(六十九)JNI实战

    NDK NDK的用途 NDK全称为Native Development Kit,意即原生的开发工具,NDK允许开发者在APP中通过C/C++代码执行部分程序.它是Android提供的方便开发者通过JN ...

  6. Android开发笔记(六十四)网页加载与JS调用

    内置浏览器 网页视图WebView 如果一个网站已经有现成的网页及业务逻辑,那么使用WebView将其内嵌到app中,省去了app重画页面与http通信的事情,无疑是更经济的做法.WebView就是A ...

  7. Android开发笔记(八十六)几个特殊的类

    接口interface interface是一些功能的集合,但它只定义了对象必须实现的成员,而不包含成员的实现代码,成员的具体代码由实现接口的类提供.Android对接口的使用场景主要有三类:事件监听 ...

  8. Android开发笔记(六十一)文件下载管理DownloadManager

    下载管理DownloadManager 文件下载其实是网络数据访问的一种特殊形式,使用普通的http请求也能完成,就是实现起来会繁琐一些.因为下载功能比较常用,而且业务功能相对统一,所以从Androi ...

  9. Android开发笔记(九十)建造者模式

    基本概念 建造者模式是一种常用的设计模式,它用于把类的表现和构建分离开来.引入建造者模式的缘由,且看博主下面细细道来. 公开属性 一般我们定义一个类的属性,如果属性是公开的,那可以直接对该类的属性赋值 ...

最新文章

  1. 生成打印条码_条码打印软件如何生成跳号条形码
  2. spring boot整合spring5-webflux从0开始的实战及源码解析
  3. PHP增加$_ENV变量
  4. android 代码中使用dp,简单谈谈Android中SP与DP的区别
  5. matlab中符号对象的数据类型是,符号对象(Symbolic Object)的使用
  6. mysql close conn_mysql CloseConnection问题
  7. 区块链100讲:带你走进EOS的存储系统
  8. SQL Server 默认跟踪应用4 -- 检测日志文件自动增长
  9. ASTC纹理压缩格式介绍
  10. POJ 3987 Computer Virus on Planet Pandora (AC自动机优化)
  11. centos7图形化分区和ks文件分区的配置
  12. wyh2000 and pupil
  13. Element组件框架
  14. Java中 关键字abstract(抽像)的定义
  15. dminit方式初始化实例时出现创建文件夹失败问题该如何处理?/初始化实例失败/fail to init db。
  16. 运放参数 分析 (LMC6482 为例)
  17. C++核心编程笔记——内存分区模型(出自b站黑马程序员视频)
  18. vs 番茄助手设置 document method 快捷方法注释
  19. Python批量ping脚本
  20. python爬取”药智数据”网站下疾病分类与代码的所有疾病名称

热门文章

  1. TensorFlow2.0: keras.metrics的使用
  2. TensorFlow2.0:常用数据范围压缩函数
  3. PBRNet:Progressive Boundary Refinement Network for Temporal Action Detection (AAAI 2020)
  4. 聚类算法—K-means python实现
  5. 吴恩达机器学习作业Python实现(二):logistic回归
  6. 用WPF实现在ListView中的鼠标悬停Tooltip显示
  7. 若依前后端分离如何修改title标题呢?
  8. 本地mysql数据库初始密码_忘记本地MySQL数据库密码的解决方法
  9. c++ 对象起始地址 指针靠齐_Go的内存对齐和指针运算详解和实践
  10. 条件表达式计算个人所得税c语言,个税的计算C语言实现,结果为什么是负的?...