After Doze Mode was introduced in Android 8, Android has enforced many restrictions on background work which makes the app behave in an unintended way.
In one of my apps , I used to fetch Battery Level every 15 mins and fire an alarm if battery level was above the desired battery level set by the user in my app. I used the following code to accomplish this task.
set up an repeating alarm using AlarmManagerApi :
public static void setAlarm(final Context context) {
final AlarmManager manager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
if (manager != null) {
manager.setRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP, 0, ALARM_INTERVAL,
getAlarmPendingIntent(context));
}
}
where getAlarmPendingIntent method is as follows:
private static PendingIntent getAlarmPendingIntent(final Context context) {
return PendingIntent.getBroadcast(context, REQUEST_CODE, new Intent(context, AlarmReceiver.class), PendingIntent.FLAG_UPDATE_CURRENT);
}
Setup a BroadcastReciever to recieve alarms: It starts a service in the foreground to do the required background work
public class AlarmReceiver extends BroadcastReceiver {
@Override
public void onReceive(final Context context, final Intent intent) {
if (context == null || intent == null) {
return;
}
if (intent.getAction() == null) {
Intent monitorIntent = new Intent(context, TaskService.class);
monitorIntent.putExtra(YourService.BATTERY_UPDATE, true);
ContextCompat.startForegroundService(context, monitorIntent);
}
}
}
TaskService(Service doing some desired task ) is as follows
public class TaskService extends Service {
private static final String CHANNEL_ID = "channel_01";
private static final int NOTIFICATION_ID = 12345678;
@Override
public void onCreate() {
super.onCreate();
createNotificationChannel()
}
public void createNotificationChannel(){
// create notification channel for notifications
NotificationManager mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
CharSequence name = getString(R.string.app_name);
NotificationChannel mChannel =
new NotificationChannel(CHANNEL_ID, name, NotificationManager.IMPORTANCE_DEFAULT);
if (mNotificationManager != null) {
mNotificationManager.createNotificationChannel(mChannel);
}
}
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
startForeground(NOTIFICATION_ID, getNotification());
BatteryCheckAsync batteryCheckAsync = new BatteryCheckAsync();
batteryCheckAsync.execute();
return START_STICKY;
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
private Notification getNotification() {
NotificationCompat.Builder builder = new NotificationCompat.Builder(this, CHANNEL_ID)
.setContentText(text)
.setContentTitle("fetching battery level")
.setOngoing(true)
.setPriority(Notification.PRIORITY_LOW)
.setSmallIcon(R.mipmap.ic_launcher)
.setWhen(System.currentTimeMillis());
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
builder.setChannelId(CHANNEL_ID);
}
return builder.build();
}
private class BatteryCheckAsync extends AsyncTask<Void, Void, Void> {
@Override
protected Void doInBackground(Void... arg0) {
IntentFilter ifilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
Intent batteryStatus = YourService.this.registerReceiver(null, ifilter);
int level = batteryStatus.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
int scale = batteryStatus.getIntExtra(BatteryManager.EXTRA_SCALE, -1);
Log.i("Battery Charge Level",String.valueOf((level / (float) scale)));
return null;
}
protected void onPostExecute() {
YourService.this.stopSelf();
}
}
}
BatteryCheckAsync task gets battery level from background and shows current battery level in the logs .
Now, if I run the app and swipe the app off my Recent App List and turn off the screen of the device .
The app prints the logs with battery level in android versions before Android 8 . But after android 8 , this stopped working within minutes of swiping the app off my recent app list and turning off the screen of device. The above behaviour was expected as Doze mode was introduced in android and alarms are deferred until next maintenance window .
=================================================================
From the android documentation on https://developer.android.com/training/monitoring-device-state/doze-standby, I found it documented that
Standard AlarmManager alarms (including setExact() and setWindow()) are deferred to the next maintenance window.
If you need to set alarms that fire while in Doze, use setAndAllowWhileIdle() or setExactAndAllowWhileIdle().
Alarms set with setAlarmClock() continue to fire normally — the system exits Doze shortly before those alarms fire.
So, I modified my code as follows now :
public static void setAlarm(final Context context) {
final AlarmManager manager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
if (manager == null) {
return;
}
int SDK_INT = Build.VERSION.SDK_INT;
if (SDK_INT < Build.VERSION_CODES.KITKAT) {
manager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, ALARM_INTERVAL, getAlarmPendingIntent(context));
} else if (SDK_INT < Build.VERSION_CODES.M) {
manager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP, ALARM_INTERVAL, getAlarmPendingIntent(context));
} else {
manager.setExactAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME_WAKEUP, ALARM_INTERVAL, getAlarmPendingIntent(context));
}
}
rather than setting a repeating alarm , I set a One time exact alarm and scheduling the next one myself when handling each alarm delivery in the onRecieve method of broadcast reciever by running the above setAlarm() method again.
Now, if I run the app and swipe the app off my Recent App List on OnePlus 3 and turn off the screen of the device , the app prints logs for about 40-45 mins and then suddenly the alarms and foreground service stops running.
Does any one have a solution for guaranteed execution of periodic background task for indefinite period of time in Android Oreo and above? I am Testing it on OnePlus Devices with Android Oreo or above installed.
You may also take advantage of WorkManager i.e:
and use it like this:
for more info visit https://developer.android.com/topic/libraries/architecture/workmanager/basics