0

I know there are dozens of similar threads on SO about this topic but I just couldn't find one that really solves the problem / or identifies the root cause.

First of all, I'm targetting SDK 22 (Android 5.1) which means I could use the AlarmManager + WakefulBroadcastReceiver + IntentService even if this is not the latest way of doing things. I'm not interested in the JobScheduler etc solutions, I just want to understand what is happening and why.

The phone I'm testing on has Android 8.0, but it shouldn't matter as I'm targeting Android 5.1.

So the code I'm dealing with sets the alarm for the next day, 06:00.

private fun setupAlarm() {
    val calendar = Calendar.getInstance()
    calendar.timeInMillis = System.currentTimeMillis()
    calendar.add(Calendar.DAY_OF_YEAR, 1)
    calendar.set(Calendar.HOUR_OF_DAY, 6)
    calendar.set(Calendar.MINUTE, 0)

    val alarmIntent = Intent(this, AlarmReceiver::class.java)
    val alarmPendingIntent = PendingIntent.getBroadcast(this, 1221, alarmIntent, PendingIntent.FLAG_UPDATE_CURRENT)

    val alarmManager = getSystemService(Context.ALARM_SERVICE) as AlarmManager
    alarmManager.setRepeating(AlarmManager.RTC_WAKEUP, calendar.timeInMillis, AlarmManager.INTERVAL_DAY, alarmPendingIntent)
}

The AlarmReciever only starts a service:

class AlarmReceiver : WakefulBroadcastReceiver() {
    override fun onReceive(context: Context?, intent: Intent?) {
        startWakefulService(context, Intent(context, DownloadingIntentService::class.java));
    }
}

This Service then tries to download a file, when finished it calls the completeWakefulIntent(intent) method letting know the system that it's done with its job.

I could not figure out when it is working and when it is not. One morning it did what it should have, on the other, it didn't.

I set up a remote LogCat feature to see whether the IntentService is started but so far I can't see any logs from it, so it means that the alarm is not triggered.

If I set up an alarm for the next minute, even repeating one whatever it works like it should. But when I set back the time for tomorrow morning then it's very unreliable.

Thanks for your help.

Peter Szucs
  • 1,116
  • 9
  • 18

4 Answers4

3

I've faced this exact issue myself. See what happens is that setRepeating method let's the android system adjust the time when the alarm should get fired. It will most likely try to batch different alarms in order to optimise battery usage. But in regular cases, if the phone isn't dozing... It generally fires the alarm at correct times.

However if the phone has been idle for a time, the phone goes into doze mode and due to this the alarm gets delayed. I have personally observed delays of upto 1 1:30 hours.

If you want it to fire exactly, you'll have to use the setExactAndAllowWhileIdle method or setAlarmClock method. In this case, you will have to handle the scheduling of your next alarm on your own. The methods work well with doze mode and do fire the alarms at exact times.

There are cons to these methods too. The setExactAndAllowWhileIdle method can only be used to schedule alarms Max once per nine minutes or so. The setAlarmClock method will mostly show a notification like a regular alarm to the user and will indicate the details of the alarm ( this behaviour varies with different os versions )

Kushan
  • 5,855
  • 3
  • 31
  • 45
  • Yeah, I've read similar answers connected with the doze mode. But it should affect the app only if the app is targeting API 23 or later, nope? I thought if I target android 22, so the point is that it's less then when doze was introduced then it shouldn't consider dozing. – Peter Szucs Feb 06 '18 at 09:35
  • Just read something about that "Beginning with API 19 (KITKAT) alarm delivery is inexact .... " on the https://developer.android.com/reference/android/app/AlarmManager.html So might be this. – Peter Szucs Feb 06 '18 at 09:36
  • No, the doze mode does kick in for all apps unfortunately. At least that's my own observation. No matter what the docs say, the alarms do get erratic with the doze mode no matter what os you are targeting – Kushan Feb 06 '18 at 09:39
  • @Kushan, I am also using android 5.1, which means API level 22. setExactAndAllowWhileIdle doesn't exist in 22 (only 23). Do you have any suggestions ? – Code Wiget Dec 12 '19 at 15:08
  • doze mode is not present inside api 22 so you can simply use setExact and it should work fine on that api level for 23 and above use the setExactAndAllowWhileIdle – Kushan Dec 13 '19 at 07:29
1

I used this code to trigger a backup every day. It is working for me, Give it a try.

AlarmManager alarmMgr = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE);
    Intent intent = new Intent(context, MyReceiver.class);
    intent.setAction("CUSTOM_INTENT");
    alarmIntent = PendingIntent.getBroadcast(context, 0, intent, 0);

    Calendar calendar = Calendar.getInstance();
    calendar.setTimeInMillis(System.currentTimeMillis());
    calendar.set(Calendar.HOUR_OF_DAY, 06);
    calendar.set(Calendar.MINUTE, 00);

    // setRepeating() lets you specify a precise custom interval--in this case,
    // 1 day
    alarmMgr.setRepeating(AlarmManager.RTC, calendar.getTimeInMillis()/1000,
            AlarmManager.INTERVAL_DAY, alarmIntent);
Zankrut Parmar
  • 1,872
  • 1
  • 13
  • 28
  • If I remove that line it will trigger the alarm straight away if 6am is already passed. As I do not want it to be triggered right away it should stay there. If I log the calendar's time it shows the right time (next day 6am) so it should be ok. – Peter Szucs Feb 06 '18 at 08:58
  • I updated my answer check it tell me if it is working or not. – Zankrut Parmar Feb 06 '18 at 09:02
  • I've used this kind of setup, but as I said if 6 am is before the time when this piece of code runs then it will trigger the alarm straight away. This is unwanted. I'm doing now the same just adding 1 more day to the calendar. the time is set correctly. (if I log the calendar) – Peter Szucs Feb 06 '18 at 09:16
  • The problem is more related something how the alarm is set, and how android handles alarms. – Peter Szucs Feb 06 '18 at 09:16
  • quote from https://developer.android.com/training/scheduling/alarms.html "A trigger time. If the trigger time you specify is in the past, the alarm triggers immediately." – Peter Szucs Feb 06 '18 at 09:18
  • Okay I am trying to make a demo project for you just give me some time. – Zankrut Parmar Feb 06 '18 at 09:20
  • Yes you are right It is triggering the event right away. – Zankrut Parmar Feb 06 '18 at 09:26
0

Try this:

    Calendar now = Calendar.getInstance();
        Calendar alarm = Calendar.getInstance();
        alarm.set(Calendar.HOUR_OF_DAY, hourOfDay);
        alarm.set(Calendar.MINUTE, minute);
        long alarmMillis = alarm.getTimeInMillis();
        if (alarm.before(now)) alarmMillis+= 86400000L;  //It will add 1 day if your time selected before now
        //set alarm method of yours\\

settingAlarmManager(requestCode, alarmMillis);

private void settingAlarmManager(String requestCode, Calendar calendar) {
        AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
        Intent notificationIntent = new Intent(AddTaskActivity.this, AlarmReceiver.class);
        PendingIntent broadcast = PendingIntent.getBroadcast(AddTaskActivity.this,
                Integer.valueOf(requestCode), notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT);
        alarmManager.setInexactRepeating(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(), AlarmManager.INTERVAL_DAY, broadcast);
  }

My Receiver class:

public class AlarmReceiver extends BroadcastReceiver {


    @Override
    public void onReceive(Context context, Intent intent) {
        //do your stuff here\\
    }
}

Manifest:

<receiver android:name=".utils.AlarmReceiver" />

I have tried above code for setting alarm and do my custom task. Maybe this will help you.

Rahul Singh Chandrabhan
  • 2,531
  • 5
  • 22
  • 33
  • Thanks, Raul, but the Calendar's time is correct if I log it. I do believe the problem is somewhere else, connected with the idle/not idle state of the phone. (Something like DOZE mode, although it cannot affect this as I'm not targeting API 23) – Peter Szucs Feb 06 '18 at 09:10
-1

A few things:

  • The further in time an Alarm is scheduled, the less precise it will be.
  • Although you are targeting API Level 22, deprecated elements in higher Android version may not fully work, which is the case of the WakefulBroadcastReceiver
  • You are trying to run a background job in Android 8.0. It's worth exploring the Foreground Service:

    • It can be started from the background
    • Correctly notify users that you are indeed doing something while the phone should be idle.
    • Do not fear running tasks that might take a few seconds to complete.
  • You might have killed your application. When a user manually kills an app, in most devices all Alarm's and PendingIntent's are killed as well.


A scheduling strategy many developer use is not to set a repeating Alarm, yet have two single Alarms that reschedule each other continuously ( @Kushan mentioned something similar in his answer).

In short:

Have a Scheduler start as soon as possible during the day (even when a user opens your app, it can be fired multiple times). Check if the desired PendingIntents already exist (your background jobs). If they do not, just schedule them. As well, schedule another Alarm around 11.55.

All this midnight scheduler has to do, is to re-schedule the main AlarmManager in 5 minutes, which is then going to schedule the jobs and the midnight alarm for the next days.

This method allows you to:

  • schedule exact alarms, since repeating ones do not have the exact option.
  • reduce the time distance of your scheduled alarms, which will then generally be treated with more precision by the OS.
  • avoid alarms that trigger immediately because scheduled in the past

Also, try to get the most from the API version you are using:

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        this.alarmManager.setExactAndAllowWhileIdle(
                AlarmManager.RTC_WAKEUP,
                rtcStartingTime.getTimeInMillis(),
                pendingIntent
        );
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
        this.alarmManager.setExact(
                AlarmManager.RTC_WAKEUP,
                rtcStartingTime.getTimeInMillis(),
                pendingIntent
        );
} else {
        this.alarmManager.set(
                AlarmManager.RTC_WAKEUP,
                rtcStartingTime.getTimeInMillis(),
                pendingIntent
        );
}
payloc91
  • 3,724
  • 1
  • 17
  • 45