2

I have a requirement to perform a task at exactly every 5 minutes. I have considered multiple options and have attempted to implement this using the AlarmManager class to trigger the task. However I cannot get the alarm to trigger when the application has been killed.

The alarm seems to work flawlessly when the app is open, or running in the background, but as soon as I exit the app, it seems to completely stop.

My implementation is to use the setExactAndAllowWhileIdle() function and to handle the repeating of this myself. The initial alarm is triggered after 5 seconds, then every 5 minutes after this.

I have tested this with 5 and 10 minute increments, but again this never runs when the app is closed.

Please take a look at my implementation:

My Activity.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
}
Elletlar
  • 3,136
  • 7
  • 32
  • 38
Indiana
  • 683
  • 7
  • 18
  • You can look at my question that I asked yesterday : https://stackoverflow.com/questions/63303559/how-to-show-daily-offline-notifications-in-android-10 – null_override Aug 08 '20 at 09:58

1 Answers1

2

The problem is that the code is calling startService() while the app is in the background.

This was allowed prior to Android 8, but now is subject to the following restrictions:

While an app is in the foreground, it can create and run both foreground and background services freely. When an app goes into the background, it has a window of several minutes in which it is still allowed to create and use services. At the end of that window, the app is considered to be idle. At this time, the system stops the app's background services, just as if the app had called the services' Service.stopSelf() methods.

Background Execution Restrictions

The service created in the sample above is a "Background Service" in the language of the Android docs because it does not call startForeground to post a Notification to make the user aware that it is running.

To start a "Foreground service" from an alarm while the app is in the background, ContextCompat.startForegroundSerivce must be used. It will call startForegroundService on Android 8 and above and startService on older devices.

In the service, you will need to call startForeground to post an ongoing service Notification to make the user aware that the service is running. If the Notification is not posted, the service will be killed after 5 seconds.

It would also worth considering whether the task can be accomplished via WorkManager or with Firebase Cloud Messaging.

Lastly, you probably need to inform your client that running a task "exactly every 5 minutes" is not possible on modern Android devices. I have not looked at the Doze implementation recently, but in the past have observed routine delays of up 10 minutes during maintenance windows when using setExactAndAllowWhileIdle. But the delays can be longer under certain circumstances.

Regarding onReceive not being called in the BroadcastReceiver:

Disable Battery Optimisations

Do not kill my app

Lastly, you could try passing in a unique requestCode in getBroadcast instead of passing 0 each time.

Elletlar
  • 3,136
  • 7
  • 32
  • 38
  • Thanks for your detailed response. This is interesting but not sure if it is exactly my issue here. When my app goes into the background this task continues to work as expected however after I kill the app, I no longer get any of the log messages printing. I note that the log tag in my broadcast receiver is not being printed out, so I'm not even getting to the .startService() call. It seems as if the alarms are getting cleared completely, rather than actually functioning but unable to start the service. – Indiana Aug 08 '20 at 07:23
  • No problem. It will be the next problem. Using startService() will not work. I will add something about the alarm now. – Elletlar Aug 08 '20 at 09:48