我要求每5分钟执行一次任务。我考虑了多个选项,并尝试使用AlarmManager
类来触发该任务。然而,当应用程序被终止时,我无法触发警报。
当应用程序打开或在后台运行时,警报似乎完美地工作,但我一退出应用程序,它似乎就完全停止了。
我的实现是使用setExactAndAllowWhileIdle()
函数,并自己处理此函数的重复。初始警报在5秒钟后触发,此后每5分钟触发一次。
我已经用5分钟和10分钟的增量进行了测试,但同样,当应用程序关闭时,它永远不会运行。
请看一下我的实现:
我的活动.kt:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_landing)
initAlarm()
}
private fun initAlarm() {
val alarm = getSystemService(Context.ALARM_SERVICE) as AlarmManager
val intent = Intent(this, AlarmReceiver::class.java).apply { action = "MY_ALARM" }
val sender = PendingIntent.getBroadcast(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
val ALARM_DELAY_IN_SECOND = 5
val alarmTimeAtUTC = System.currentTimeMillis() + ALARM_DELAY_IN_SECOND * 1_000
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
Log.(TAG, "initAlarm() 23+ - $alarmTimeAtUTC")
alarm.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, alarmTimeAtUTC, sender)
} else {
alarm.setExact(AlarmManager.RTC_WAKEUP, alarmTimeAtUTC, sender)
}
}
AlarmReceiver.kt:
class AlarmReceiver : BroadcastReceiver() {
companion object {
private val TAG = AlarmReceiver::class.java.simpleName
}
override fun onReceive(context: Context?, intent: Intent?) {
Log.d(TAG, "onReceive()")
if (intent?.action == "MY_ALARM") {
Log.d(TAG, "onReceive() - starting service")
context?.startService(Intent(context, MyService::class.java))
initAlarm(context)
}
}
private fun initAlarm(context: Context?) {
val alarm = context?.applicationContext?.getSystemService(Context.ALARM_SERVICE) as AlarmManager
val intent = Intent(context, AlarmReceiver::class.java).apply { action = "MY_ALARM" }
val sender = PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
val ALARM_DELAY_IN_SECOND = 600
val alarmTimeAtUTC = System.currentTimeMillis() + ALARM_DELAY_IN_SECOND * 1_000
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
Log.d(TAG, "initAlarm() 23+ - $alarmTimeAtUTC")
alarm.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, alarmTimeAtUTC, sender)
} else {
alarm.setExact(AlarmManager.RTC_WAKEUP, alarmTimeAtUTC, sender)
}
}
}
MyService.kt:
override fun onCreate() {
super.onCreate()
Log.d(TAG, "onCreate()")
doMyTask()
}
private fun doMyTask() {
job = CoroutineScope(Dispatchers.IO).launch {
// Perform task here and once complete stop service
stopSelf()
}
}
}
override fun onDestroy() {
super.onDestroy()
Log.d(TAG, "onDestroy()")
job?.cancel()
job = null
}
问题是,当应用程序处于后台时,代码正在调用startService()
。
这在安卓8之前是允许的,但现在受到以下限制:
当应用程序处于前台时,它可以创建和运行前台和后台服务免费。当应用程序进入背景,它有一个几分钟的窗口,在其中它是静止的允许创建和使用服务。在该窗口的末尾,应用程序被认为是空闲的。此时,系统会停止应用程序的后台服务,就像应用程序调用了服务一样Service.stopSelf((方法。
后台执行限制
在上面的示例中创建的服务是";后台服务";因为它不调用startForeground
来发布Notification
以让用户知道它正在运行。
为了开始一个";前台服务";当应用程序处于后台时,必须使用ContextCompat.startForegroundSerivce
。它将在Android 8及以上版本上调用startForegroundService
,在旧设备上调用startService
。
在该服务中,您需要调用startForeground
来发布正在进行的服务Notification
,以让用户知道该服务正在运行。如果通知没有发布,服务将在5秒钟后终止。
同样值得考虑的是,该任务是否可以通过WorkManager
或使用Firebase Cloud Messaging
来完成。
最后,你可能需要通知你的客户,运行一个任务";正好每5分钟";在现代安卓设备上是不可能的。我最近没有研究过Doze
的实现,但在过去,当使用setExactAndAllowWhileIdle
时,在维护窗口期间,我观察到了长达10分钟的例行延迟。但在某些情况下,延迟时间可能会更长。
关于未在BroadcastReceiver:中调用onReceive
禁用电池优化
不要杀死我的应用
最后,您可以尝试在getBroadcast
中传递一个唯一的requestCode,而不是每次传递0。