背景:使用 AlarmManager 给系统设置定时关机,延时了1个多小时的才执行的问题
接到问题后,首先排查了代码,看到代码使用的是 Android 自带的 AlarmManager 设置一个服务的 PendingIntent,当时间到了,则执行关机程序。代码如下:
/*** 设置定时关机* @param time 多少ms后关机*/fun shutDownSystem(time: Long) {val intent = Intent(WebConfigApi.context,ShutdownService::class.java)val pendingIntent = PendingIntent.getService(context, 0, intent,PendingIntent.FLAG_UPDATE_CURRENT)if (time <= 0) {alarm.cancel(pendingIntent)} else {alarm.set(AlarmManager.RTC_WAKEUP, time, pendingIntent)}}
从代码逻辑看,没啥问题。
一开始怀疑是客户设置了 ntp 服务,导致系统时间延时引起,但 ntp 只是为了校准时间,问了客户时间也是正常的。
从日志看,也不是必现的,所以写了一个定时开关机的脚本去压测,每次开机后对当前时间+5分钟,做定时开关机压测,测试了两天,都能开机关机。
从这里反应,程序应该是没问题,而且出货之前也测试过这块内容。
但从客户日志来,有一个跟脚本和测试明显的不同的区分,就是客户设置定时关机的跨度比较大,且设完一段时间未使用设备。
所以找了几个盒子,进行煲机,确实复现到了
但为啥时间跨度大,未使用设备就会出现?
说明软件程序确实有问题,跟踪进去,发现 set 方法,在 API 19,使用这个方法传递的时间视为不准确,系统为了减少电池消耗,可能会把这个 set 推迟,然后批量发送;
从这里可以看到,确实是set 这个方法引起的;
所以,从源码出发,发现 setExact 这个方法,该方法指定系统在规定时间内发出,不允许系统调整交付时间。
修改后,问题解决。
思考:set 方法有问题,那可能标志位也有问题。可以发现 RTC_WAKEUP 标识使用的是 系统时间,也就是 System.currentTimeMillis(),这样设置的时间,容易被人修改;所以采用 ELAPSED_REALTIME_WAKEUP 这个标志位,它根据系统开机时间来计算,也就是 SystemClock.elapsedRealtime()。
所以,代码这里,关机的时间也需要改一下,从开机时间+设置的时间即可,如下:
/*** 设置定时关机* @param time 多少ms后关机*/fun shutDownSystem(time: Long) {val elapsedTime = SystemClock.elapsedRealtime()//开机时间 + 延时时间val alarmTime = elapsedTime + timeval intent = Intent(WebConfigApi.context,ShutdownService::class.java)val pendingIntent = PendingIntent.getService(context, 0, intent,PendingIntent.FLAG_UPDATE_CURRENT)if (alarmTime <= 0) {alarm.cancel(pendingIntent)isDelayShutdown = false} else {//ELAPSED_REALTIME_WAKEUP 自开机其开始算时间,RTC_WAKEUP 则是根据系统时间有关// alarm.setExact()alarm.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP, alarmTime, pendingIntent)}}