对Android中AlarmManager保活的思考

最近有一个业务需求,就是即使app在后台,也要每隔4min向服务器发送一次数据,所以我这几天一直在研究安卓的保活措施,终于在昨天把这个问题解决了。

首先是service的保活,这个网上方法很多,就不多说了,比如在AndroidManifest里设置persistent和priority,以及前台service。这篇文章主要介绍AlarmManager的保活。

安卓系统对AlarmManager每隔几年就会把功能收紧一些,从最开始的精准时间出发变成了不精准,然后又引入了省电模式等等等,最开始我也是按照文档里说的用setRepeating的方法完成开头所说的业务需求,然后发现,如果在PendingIntent中启动一个BoardcastReceiver,则这个定时器能存活大概4、5小时,随后就会被系统杀死而失效。如果在PendingIntent中启动一个Service,那就更离谱了,不到6min定时器就会失效。

在从系统层面反复尝试各种方法而无果后,我想到一个神奇操作,就是利用setExactAndAllowWhileIdle这唯一被安卓系统保留可以准时触发(但不能循环触发)的函数,让它启动一个service,同时在service里也设置一个同样的AlarmManager,同样使用setExactAndAllowWhileIdle,将其触发时间定为4min后,让他唤醒自身。我本以为安卓系统会禁止这样递归创建计时器,没想到竟然成功了,这个计时器存活了很长时间也没有被系统杀死。具体代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
//MainService
Log.d(TAG, "开始生成计时器")
val alarmManager = getSystemService(ALARM_SERVICE) as AlarmManager
val pIntent = PendingIntent.getService (
this, 0,
Intent(this, LooperService::class.java),
PendingIntent.FLAG_CANCEL_CURRENT
)
alarmManager.setExactAndAllowWhileIdle(ELAPSED_REALTIME_WAKEUP,
SystemClock.elapsedRealtime() + 2000, pIntent)

//LooperService
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
Log.d(TAG, "LooperService onStartCommand")
Log.d(TAG, "即将重新设置定时器")
val alarmManager = getSystemService(ALARM_SERVICE) as AlarmManager
val pIntent = PendingIntent.getService (
this, 0,
Intent(this, LooperService::class.java),
PendingIntent.FLAG_CANCEL_CURRENT
)
alarmManager.setExactAndAllowWhileIdle(
AlarmManager.ELAPSED_REALTIME_WAKEUP,
SystemClock.elapsedRealtime() + 4*60*1000, pIntent)
return super.onStartCommand(intent, flags, startId)
}

但是,经测试,设备在晚上不充电时,过了凌晨0:00 App无法上报数据,推测原因可能是app被系统冻结或者系统夜间自动断开网络等原因,在上午连接充电器后会自动恢复。