美文网首页功能控件managerandroid工具类
关于使用AlarmManager的注意事项

关于使用AlarmManager的注意事项

作者: 俞其荣 | 来源:发表于2017-01-23 23:53 被阅读3444次

    博文出处:关于使用AlarmManager的注意事项,欢迎大家关注我的博客,谢谢!

    快过年了,更新春节前的最后一篇博客。

    最近在做一个需求:客户端按照规定的时间间隔向服务端发送定位。一看到这个需求就想到了使用 AlarmManager 来实现。 AlarmManager 经常被用来执行定时任务,比如设置闹铃、发送心跳包等。也许有人会有疑问:为什么不能使用相同具有定时效果的 TimerHandler 呢?

    其实答案非常简单,相对于 Handler 来说,使用 sendEmptyMessageDelayed 方法是依赖于 Handler 所在的线程的,如果线程结束,就起不到定时任务的效果;而 AlarmManager 依赖的是 Android 系统的服务,具备唤醒机制。比起 Handler 也就更合适了。

    而至于 Timer 可以精确地做到定时操作,但是相比于 AlarmManager 而言还是差了一截。同理,如果手机关屏后长时间不使用, CPU 就会进入休眠模式。这个使用如果使用 Timer 来执行定时任务就会失败,因为 Timer 无法唤醒 CPU 。

    所以,综上所述,AlarmManager 就成为了最佳选择。

    SDK API < 19

    一般情况下,使用 AlarmManager 来执行重复定时任务的代码如下所示:

    alarmManager.setRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime(), TIME_INTERVAL, pendingIntent);
    

    或者

    alarmManager.setRepeating(AlarmManager.RTC_WAKEUP, System.currentTimeMillis(), TIME_INTERVAL, pendingIntent);
    

    setRepeating 该方法用于设置重复定时任务。

    • 第一个参数表示闹钟类型:一般为 AlarmManager.ELAPSED_REALTIME_WAKEUP 或者 AlarmManager.RTC_WAKEUP 。它们之间的区别就是前者是从手机开机后的时间,包含了手机睡眠时间;而后者使用的就是手机系统设置中的时间。所以如果设置为 AlarmManager.RTC_WAKEUP ,那么可以通过修改手机系统的时间来提前触发定时事件。另外,对于相似的 AlarmManager.ELAPSED_REALTIMEAlarmManager.RTC 来说,它们不会唤醒 CPU 。所以使用的频率较少;
    • 第二个参数表示任务首次执行时间:与第一个参数密切相关。第一个参数若为 AlarmManager.ELAPSED_REALTIME_WAKEUP ,那么当前时间就为 SystemClock.elapsedRealtime() ;若为 AlarmManager.RTC_WAKEUP ,那么当前时间就为 System.currentTimeMillis()
    • 第三个参数表示两次执行的间隔时间:这个参数没什么好讲的,一般为常量;
    • 第四个参数表示对应的响应动作:一般都是去发送广播,然后在广播接收 onReceive(Context context, Intent intent) 中做相关操作。

    至此,一切顺利,畅通无阻。

    SDK API >= 19 && SDK API < 23

    当你写好代码、满心欢喜地将程序跑在手机上的时候,傻眼了!你会发现在 Android 4.4 及以上版本的定时任务不是按照规定时间间隔来执行的。比如你设置了每隔 3 分钟发出一个 HTTP 请求,结果你一看莫名其妙地变成了隔 5 分钟发一次。

    What the fuck?

    what the fuck

    然后你查阅 Android 官网中关于 Android 4.4 API 会看到如下几句话:

    Android 4.4 API

    恍然大悟!原来是 Google 为了追求系统省电,所以“偷偷加工”了一下唤醒的时间间隔。但也正如上面官网中所说的那样,如果在 Android 4.4 及以上的设备还要追求精准的闹钟定时任务,要使用 setExact() 方法。

    所以,相应的代码就变成了这样:

    // pendingIntent 为发送广播
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
        alarmManager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime(), pendingIntent);
    } else {
        alarmManager.setRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime(), TIME_INTERVAL, pendingIntent);
    }
    
    private BroadcastReceiver alarmReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            // 重复定时任务
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
                alarmManager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() + TIME_INTERVAL, pendingIntent);
            }
            // to do something
            doSomething();
        }
    };
    

    当你写好了“加强版”的 AlarmManager 之后,内心肯定无比小激动。这下总应该行了吧?运行一下,果然没错!在 Android 4.4 上的确按照规定的时间间隔在执行任务。哈哈,这下大功告成了!!!

    SDK API >= 23

    在 Android 4.4 上品尝到胜利的甜头后,你顺便在 Android 6.0 的设备上测试了一下。结果。。。。。。你又 TMD 傻眼了!

    What the fuck

    发现在设备关屏静止一段时间后, AlarmManager 又又又不能正常工作了。相必此时你连日狗的心都有了吧!强忍着泪水,再次打开 Android 官网中关于 Android 6.0 变更 ,发现在 Android 6.0 中引入了低电耗模式和应用待机模式。然后接着往下看 对低电耗模式和应用待机模式进行针对性优化 ,发现会有下面一段话:

    Android 6.0 API

    啊啊啊啊啊啊!之前在 Android 4.4 上能用的 setExact() 方法在 Android 6.0 上因为低电耗模式又不能正常使用了。但是,Google 又又又提供了新的方法 setExactAndAllowWhileIdle() 来解决在低电耗模式下的闹钟触发。

    所以,Attention!相关的代码又被改写为这样:

    // pendingIntent 为发送广播
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        alarmManager.setExactAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime(), pendingIntent);
    } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
        alarmManager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime(), pendingIntent);
    } else {
        alarmManager.setRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime(), TIME_INTERVAL, pendingIntent);
    }
    
    private BroadcastReceiver alarmReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            // 重复定时任务
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                alarmManager.setExactAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() + TIME_INTERVAL, pendingIntent);
            } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
                alarmManager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() + TIME_INTERVAL, pendingIntent);
            }
            // to do something
            doSomething();
        }
    };
    

    到了这里,总算是把因 Android 版本差异导致 AlarmManager 的“坑”填完了。这份代码已经可以满足日常的重复定时任务了。

    好了,该讲的都讲完了,上床睡觉。仓促地结尾,预祝大家新年快乐!

    Goodbye !

    References

    相关文章

      网友评论

      • ae1f54e94bd7:有时候安卓的适配问题能让你有种劳资再也不要做安卓开发的感觉:joy:
      • WillBars:你好!博主
        PendingIntent pi = PendingIntent.getBroadcast(context, 0, i, 0);这里是通过广播去获取PendingIntent,然后通过接收广播去实现活动,我想问下,博主整个大类是建立在服务下,还是在activity下?烦请告知下
      • 3674d0183b56:不错,有用,谢谢!只是杀了APP,这个就无效了,只能让它驻留在历史窗口中。。
        流星灬飞雨:什么叫历史窗口
      • 菊花与刀:我按照上面的写法,在应用进入后台 设备锁屏的情况下一段时间后 定时任务就失效了
        流星灬飞雨:你怎么解决的?我发现部分魅族手机锁屏一段时间 定时任务就失效 正常的handler 一分钟内就失效
      • 蔡员外:在Android 6.0上此接口 alarmManager.setExactAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime(), pendingIntent);我设为每两分钟提醒一次,在设备进入深度休眠时,alarm执行的时间不精准,请问你有能精准间隔时间执行的demo吗?
      • 丶Mdzz:那4.4以上setRepeading咋办
        俞其荣:@丶Mdzz 就是唤醒时间间隔被对齐了,方便设备省电
        丶Mdzz: @俞其荣 我发现也不是不准确,我现在项目里明明设置了一分钟重复,可现在总是两分钟一次重复😭
        俞其荣:4.4 以上的 setRepeating 不准确了啊
      • 湫水长天:写得真好。不过话说回来,Google 对于功能的向下兼容性做得不如微软啊。。。

      本文标题:关于使用AlarmManager的注意事项

      本文链接:https://www.haomeiwen.com/subject/zeczbttx.html