Search code examples
androidalarmmanager

AlarmManager funcions on API 24 but not on API 28


I'm using Androids AlarmManager to set off an event at 5 minute intervals. This works fine on my api 24 phone but fails to function on my api 28 phone. When running on the api 28 phone dumpsys alarm shows that the alarms are going off but the JobIntentService passed in their intent is never called.

So far I've tried different ways of setting the alarm, such as setAndAllowWhileIdle with no effect. Running dumpsys alarm on the api 28 phone shows that the single shot setAlarmWhileIdle goes off but does not go off again as expected. Similarly setInexactRepeating also goes off at the passed repeating interval. Both do not call the passed JobIntentService.

public class DataUploadService extends JobIntentService
    implements DataClientManager.OnDataChangedListener {

private static final String TAG = "DataUploadService";
private static void LOG(String msg) { Log.d(TAG,msg); }
private static void LOGE(String msg) { Log.e(TAG,msg); }

public static final int DELAY_PERIOD = 5;

public static void initialize(Context context) {

    //parse minutes to get exact time
    final Calendar calendar = Calendar.getInstance();
    calendar.setTimeInMillis(System.currentTimeMillis());
    calendar.add(Calendar.MINUTE,DELAY_PERIOD);

    DataUploadService.setAlarm(context,calendar.getTimeInMillis());

}

public static void setAlarm(Context context, long millis) {

    clearAlarm(context);

    AlarmManager alarmManager = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE);

    Intent intent = new Intent(context, DataUploadService.class);
    PendingIntent alarmIntent = PendingIntent.getService(context, 1, intent, PendingIntent.FLAG_UPDATE_CURRENT);

    alarmManager.setInexactRepeating(AlarmManager.RTC_WAKEUP, millis,
            DELAY_PERIOD * 60 * 1000, alarmIntent);

}

public static void clearAlarm(Context context) {
    AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);

    Intent intent = new Intent(context, DataUploadService.class);
    PendingIntent alarmIntent = PendingIntent.getService(context, 1, intent, PendingIntent.FLAG_UPDATE_CURRENT);

    alarmManager.cancel(alarmIntent);
}

@Override
protected void onHandleWork(Intent intent) {

    LOGE("Data Acquisition Service has been called");
    final Calendar calendar = Calendar.getInstance();
    calendar.setTimeInMillis(System.currentTimeMillis());
    LOG(calendar.getTime().toString());

}
}

In my MainActivity i call DataUploadService.initalize(this), after which i would expect to see in my system log "Data Acquisition Service has been called" at 5 minute intervals. This happens as desired on my api 24 phone but does not on my api 28 phone. I see no noticeable difference in the api for AlarmManager in the documentation.

Update:

I found that set(int type, long triggerAtMillis, String tag, AlarmManager.OnAlarmListener listener, Handler targetHandler) to work on my api level 28 device. Both a null and non-null Handler seem to work. This doesn't answer my question as to why JobIntentService no longer works but does provide a temporary work around.


Solution

  • I guess the problem is happening because you are scheduling the Alarm and starting a service when it expires.

    Since services are background tasks, they are a bit more restricted now (from API26+)

    So, I'm seeing two solutions:

    First Solution

    Create a Broadcast receiver (adding the intents to AndroidManifest.xml etc). So, after 5 minutes, that broadcast will receive the intent and that broadcast will start the service via:

    AndroidManifest

    <receiver
        android:name=".DataUploadBroadcast"
        android:exported="true"
        tools:ignore="ExportedReceiver">
        <intent-filter>
            <action android:name="MY_CUSTOM_ACTION" />
        </intent-filter>
    </receiver>
    

    BroadcastReceiver

    public class DataUploadBroadcast extends BroadcastReceiver {
    
        public static final int DELAY_PERIOD = 5;
    
        public void onReceive(Context receivedContext, Intent intent) {
            if("MY_CUSTOM_ACTION".equals(intent.getAction() {
                DataUploadService.enqueueWork(context, new Intent(context, DataUploadService.class);
            }
        }
    
        private static void setAlarm(Context context, long millis) {
            clearAlarm(context);
            AlarmManager alarmManager = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE);
    
             // Note the class is different now
            Intent intent = new Intent(context, DataUploadBroadcast.class);
    
            // Set action
            intent.setAction("MY_CUSTOM_ACTION");
    
            // Note the getBroadcast
            PendingIntent alarmIntent = PendingIntent.getBroadcast(context, 1, intent, PendingIntent.FLAG_UPDATE_CURRENT);
            alarmManager.setInexactRepeating(AlarmManager.RTC_WAKEUP, millis, DELAY_PERIOD * 60 * 1000, alarmIntent);
        }
    
        public static void initialize(Context context) {
            // call setAlarm
        }
    
        private static void clearAlarm(Context context) {
            // Clear the alarm
        }
    }
    

    DataUploadService

    private static final int JOB_ID = 100;
    
    public static void enqueueWork(Context context, Intent work) {
        // This is how you should start the job.. and not PendingIntent.getService(...)
        enqueueWork(context, DataUploadService.class, JOB_ID, work);
    }
    
    @Override
    protected void onHandleWork(Intent intent) {
        LOGE("Data Acquisition Service has been called");
    }
    

    Second solution

    Let the service starts as soon as the Activity request to. Then, during the onHandleWork, you put the service to sleep for 5 minutes:

    public class DataUploadService extends JobIntentService
        implements DataClientManager.OnDataChangedListener {
    
        public static final int DELAY_PERIOD = 5;
        private static final int JOB_ID = 100;
    
        public static void initialize(Context context) {
            enqueueWork(context, DataUploadService.class, JOB_ID, new Intent(context, DataUploadService.class));
        }
    
        @Override
        protected void onHandleWork(Intent intent) {
            try {
                Thread.sleep(DELAY_PERIOD * 60 * 1000);
                efectivellyExecuteWork()
            } catch (InterruptedException e) {
                LOGE("ERROR");
            }
    }
    
    private void efectivellyExecuteWork() {
        LOGE("Data Acquisition Service has been called");
        final Calendar calendar = Calendar.getInstance();
        calendar.setTimeInMillis(System.currentTimeMillis());
        LOG(calendar.getTime().toString());
    }
    

    In this second solution, I just put the thread to sleep for 5 minutes. You can add some logic if you need to update that timer in case user opens the activity before it expires etc.. You can store a calendar instance with a date in the future, for example. This way, the thread sleeps until that date is reached. This would allow you to update the calendar date when necessary (acting like clearing/recreating the event);

    Something like:

    try {
        while(futureDate.getTimeInMillis() > Calendar.getInstance.getTimeInMillis()) {
            Thread.sleep(futureDate.getTimeInMillis() - Calendar.getInstance.getTimeInMillis());
        }
        efectivellyExecuteWork()
    } catch (InterruptedException e) {
        LOGE("ERROR");
    }