I've set a repeating alarm on a service and decided that it's most convenient to reset the alarm from within the called service. The reason is that the service already has code to check if it's within a user-defined schedule (time range). When it's outside the time range, it resets the alarm to start at the future time selected by the user. Maybe I'm approaching this wrong but I'll put this question out there and see what you think.
An activity kicks off the service by creating a repeating alarm:
//Activity
Calendar cal = Calendar.getInstance();
Intent intent = new Intent(getApplicationContext(), MyService.class);
intent.setData(Uri.parse("MyService://identifier"));
PendingIntent pIntent = PendingIntent.getService(getApplicationContext(), 0, intent, 0);
AlarmManager alarm = (AlarmManager)getSystemService(ALARM_SERVICE);
alarm.setInexactRepeating(AlarmManager.RTC_WAKEUP, cal.getTimeInMillis(),
intervalInMins*60000, pIntent);
The service has something like this:
//Service
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
Uri Action = intent.getData();
try {
if (Action.equals(Uri.parse("MyService://identifier"))) {
//Simplifying the code here: CalculatedOffset is determined from the current time
//and scheduled start time. intervalInMins is read from settings.
if (!WithinSchedule()) {
Calendar cal = Calendar.getInstance();
PendingIntent pIntent = PendingIntent.getService(getApplicationContext(), 0, intent, 0);
AlarmManager alarm = (AlarmManager)getSystemService(ALARM_SERVICE);
alarm.setInexactRepeating(AlarmManager.RTC_WAKEUP, cal.getTimeInMillis() + CalculatedOffset,
intervalInMins*60000, pIntent);
}
}
} catch (NullPointerException np) {
np.printStackTrace();
}
return Service.START_REDELIVER_INTENT;
}
I was hoping to re-use the intent to reset the repeating alarm. With this new code, I'm seeing multiple alarms stack up firing rapidly in succession around when the start time hits. It should not spaz out like that, but should fire at regular intervals as it did before the scheduling reset. I need to catch it in the debugger but haven't been able to determine the exact conditions yet. Is my understanding of alarms completely off base here? Is there a better way to do this?
Addendum: A wrinkle in this is that I'm using RootTools to gain superuser privileges in order to work around Android 4.2's airplane mode. This hasn't been a problem before the scheduling, but I'm suspicious whether su is blocking for a long time while the alarms stack up.
Re-using the intent inside the service that receives the alarm does work. I've switched from using a Repeating Alarm to a single-shot alarm which gets re-armed every time the service is called. Unfortunately this didn't fix the problem of the alarms stacking. The culprit is definitely su blocking. It may be RootTools or su itself. I need to update the library from 2.6 to 3.x and see if that makes any difference.
Related
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.
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 )
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);
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.
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
);
}
I'm developing an app which you can schedule your time and It reminds you on time just like google calendar. I use AlarmManager class and set a Repeating task to check Database every one minute and see if there is any alarm on that time or not.
#Override
public void onReceive(Context context, Intent intent) {
Calendar now = Calendar.getInstance();
doRepeatingWorks(now.getTimeInMillis()); // Like Checking if one day passed to do some tasks
checkDbIfThereIsSomeSchedule(now);
}
And I call this to start alarm manager:
public void setAlarm(Context context) {
AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
Intent intent = new Intent(context, AlarmReceiver.class);
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, intent, 0);
alarmManager.setRepeating(AlarmManager.RTC_WAKEUP, System.currentTimeMillis(), G.ALARM_CHECK_INTERVAL_MILIS, pendingIntent);
}
But it's inaccurate and sometimes I figure out that the task killer apps kill my alarm and make it totally worthless.
Although using a foregroundService is battery consuming and it goes on user's nerve with the notification.
Is there any solutions or alternatives for this problem?
I use AlarmManager class and set a Repeating task to check Database every one minute and see if there is any alarm on that time or not
That is a truly awful approach. This sort of behavior is precisely why Doze mode and app standby were added in Android 6.0.
Is there any solutions or alternatives for this problem?"
Schedule an alarm event for first event. When you get control, notify the user about the event, then schedule an alarm event for the next event in sequence. If the user adds a new event that is sooner than your first event, cancel the previous alarm and schedule one for the new first event.
You don't need to check there is an alarm in each 1 min. I hope this post helps you - Scheduled Alarm Manager not working Android
I have been playing with the AlarmManagerin order to schedule an IntentServiceto do X task after a specific period of time. So far it works great, here' how I do it:
public static void scheduleNextRefresh (final Context context, long msFromNow) {
Constants.logMessage("Scheduling fetcher alarm to happen within: " + msFromNow/(1000*60) + " minutes");
Intent intent = new Intent(context, AlarmReceiver.class);
intent.putExtra(AlarmReceiver.EXTRA_ACTION, AlarmReceiver.FETCH_NEWS);
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, REQUEST_CODE, intent, PendingIntent.FLAG_UPDATE_CURRENT);
AlarmManager alarm = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
if (msFromNow != -1) {
Constants.logMessage("Alarm set");
alarm.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() + msFromNow, pendingIntent);
}
}
That works well, so far; however, I normally only use small intervals such as 30 minutes, or a few hours.
Now I want to schedule an action to happen a few days in the future, and I'm curious as to whether or not it will work appropriately with the AlarmManager, or if it's simpler to use another tool to send a PendingIntent at X time.
Being that the smallest interval would be 3 days, and there may be a few reboots (I know I myself reboot my phone at least once a day) in the process, I'm not sure how practical the AlarmManager would be.
First, the logic of scheduling the refresh I am using is the same as posted above, the only difference is that I added some more code the the BroadcastReceiver in order to listen for android.permission.RECEIVE_BOOT_COMPLETED and re-schedule the alarms as appropriate.
#Override
public void onReceive(Context context, Intent intent) {
final String action = intent.getAction();
...some other code
else if (action.equals(Intent.ACTION_BOOT_COMPLETED)) {
Constants.logMessage("Re-scheduling alarms after boot completed");
SharedPreferences mPreferences = PreferenceManager.getDefaultSharedPreferences(context);
//get the current interval we are using for cleanups
long alarmInterval = Long.valueOf(mPreferences.getString("pref_key_ccleaner_interval", "259200000"));
//get the last time in miliseconds that we set an alarm
//Whenever I schedule an alarm in the IntentService
//I store the time in ms to know when it was last scheduled
long lastAlarm = mPreferences.getLong(CacheCleaner.KEY_LAST_ALARM, 0);
if (lastAlarm == 0) {
//If no previous alarm is set, schedule it normally
CacheCleaner.scheduleNextCleanup(context, alarmInterval);
}
else {
//If there was an alarm set previously
//The difference between the alarmInterval and the amount of ms ellapsed since last alarm
//is the new time we will schedule this for
CacheCleaner.scheduleNextCleanup(context, (alarmInterval - (System.currentTimeMillis() - lastAlarm)));
}
}
I think that can do the trick, but I'm just curious as to there being a better way of scheduling events to happen several days from now, without having to worry about re-scheduling, or other events.
Any ideas?
Rescheduling alarms on RECEIVE_BOOT_COMPLETED is the method I use (and has been working for over 2 years) and doing a quick search seems to validate it. AlarmManager for Android.
Alarms are not persisted across reboots. You need to run at boot time to reschedule your alarms. This is what the built-in Clock application does, for example.
As of Android 5.0, you might want to switch to using the JobScheduler mechanisms instead. With them you don't need to handle your own boot-time execution or wakelocks or anything; you just specify the timing for when you want to run (plus any other interesting constraints like wanting to run only when the device is charging), and the OS takes care of running you at the right time.
I've searched for 3 days now but didn't find a solution or similar problem/question anywhere else. Here is the deal:
Trigger in 1 hour -> works correct
Trigger in 2 hours -> Goes of in 1:23
Trigger in 1 day -> Goes of in ~11:00
So why is the AlarmManager so unpredictable and always too soon? Or what am I doing wrong? And is there another way so that it could work correctly?
This is the way I register my PendingIntent in the AlarmManager (stripped down):
AlarmManager alarmManager = (AlarmManager)parent.getSystemService(ALARM_SERVICE);
Intent myIntent = new Intent(parent, UpdateKlasRoostersService.class);
PendingIntent pendingIntent = PendingIntent.getService(parent, 0, myIntent, PendingIntent.FLAG_UPDATE_CURRENT);
//Set startdate of PendingIntent so it triggers in 10 minutes
Calendar start = Calendar.getInstance();
start.setTimeInMillis(SystemClock.elapsedRealtime());
start.add(Calendar.MINUTE, 10);
//Set interval of PendingIntent so it triggers every day
Integer interval = 1*24*60*60*1000;
//Cancel any similar instances of this PendingIntent if already scheduled
alarmManager.cancel(pendingIntent);
//Schedule PendingIntent
alarmManager.setRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP, start.getTimeInMillis(), interval, pendingIntent);
//Old way I used to schedule a PendingIntent, didn't seem to work either
//alarmManager.set(AlarmManager.RTC_WAKEUP, start.getTimeInMillis(), pendingIntent);
It would be awesome if anyone has a solution. Thanks for any help!
Update:
2 hours ago it worked to trigger it with an interval of 2 hours, but after that it triggered after 1:20 hours. It's getting really weird. I'll track the triggers down with a logfile and post it here tomorrow.
Update:
The PendingIntent is scheduled to run every 3 hours. From the log's second line it seems like an old scheduled PendingIntent is still running:
[2012-5-3 2:15:42 519] Updating Klasroosters
[2012-5-3 4:15:15 562] Updating Klasroosters
[2012-5-3 5:15:42 749] Updating Klasroosters
[2012-5-3 8:15:42 754] Updating Klasroosters
[2012-5-3 11:15:42 522] Updating Klasroosters
But, I'm sure I cancelled the scheduled PendingIntent's before I schedule a new one. And every PendingIntent isn't recreated in the same way, so it should be exactly the same. If not , this threads question isn't relevant anymore.
When using a calendar are you taking into account that the calendar uses the time right down to Milli seconds. Maybe you should set the Milli second field and the seconds field to zero so it's going of on the dot.
Also for a day it would be easier to use this
Calendar cal = Calendar.getInstance();
cal.setTimeInMillis(0);
cal.add(Calendar.DAY_OF_MONTH, 1);
Also when you use getInstance doesn't that set the calendars time to the time it was created so there shouldn't be any need to set the time again right?
Rewrite: I eventually saw your error, but unpredictably.
I did changed this:
PendingIntent.getService(parent, 0, myIntent, PendingIntent.FLAG_UPDATE_CURRENT);
to this:
PendingIntent.getService(parent, 0, myIntent, PendingIntent.FLAG_CANCEL_CURRENT);
under the same assumption as you that somehow an old intent is broadcasting. I haven't seen the fluke since...
Also the only times I saw it were during my initial call. Another approach could be to track a current and a previous Calendar object, if the interval isn't what you expected then ignore this "early" broadcast. (While this method seems redundant considering how the alarm should work, it helps prevent those extraneous calls considering how the alarm is working...)
Hope that helps, I'll let you know if I find anything else.
I know this question is a bit old, but I had this same problem myself. I found out that if I tried to declare the Calendar variable outside of the method, it wouldn't play nicely and the alarms would fire early. Because your class is stripped down it is hard to tell exactly where you're calling the calendar instance.
If I set it up as such, then it would fire right on time:
protected void nextAlarm(Context context, int seconds){
Calendar nextAlarm = Calendar.getInstance();
Intent intent = new Intent(context, MyClass.class);
PendingIntent pending = PendingIntent.getBroadcast(context, MainActivity.REPEATING_ALARM, intent, PendingIntent.FLAG_CANCEL_CURRENT);
AlarmManager amanager = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE);
nextAlarm.add(Calendar.SECOND, seconds);
amanager.set(AlarmManager.RTC_WAKEUP, nextAlarm.getTimeInMillis(), pending);
}
Make sure your service's onStartCommand returns START_NOT_STICKY, otherwise it will be automatically re-attempted:
public class UpdateKlasRoostersService extends Service {
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
buildUpdate();
return START_NOT_STICKY;
}
}
When setting a service to go off at particular time, I use the AlarmManager system service.
Everything goes off without a problem, service is called and actions take place.
When the alarm time is reached, the service starts, and at this point I get the system time (System.currentTimeMillis()). I'm guessing this wont be the actual time the service start. Is there a way to get the time that was set for this PendingIntent?
ie
Set alarm for 9am.
DoStuffService starts at 9am.
DoStuffService knows it was supposed to start at 9am, and uses this value for future functions.
When you create an intent for your alarm, you could put extra data, including time of the alarm, into it like this:
Intent intent = new Intent("action name");
//put extra data into the intent:
intent.putExtra("alarm_time_hours", hours);
intent.putExtra("alarm_time_minutes", minutes);
PendingIntent sender = PendingIntent.getBroadcast(getApplicationContext(), 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
AlarmManager am = (AlarmManager) getSystemService(ALARM_SERVICE);
am.set(AlarmManager.RTC_WAKEUP, cal.getTimeInMillis(), sender);
Then in your receiver or service you need to get this extra data from received intent. Use something like this:
Bundle bundle = intent.getExtras();
if(bundle.containsKey("alarm_time_hours")) {
int hours = bundle.getInt("alarm_time_hours");
}
if(bundle.containsKey("alarm_time_minutes")) {
int minutes = bundle.getInt("alarm_time_minutes");
}
Is there a way to get the time that was set for this PendingIntent?
No, sorry.
However, it should not be terribly difficult for you to determine it yourself. Following your example, if your service reports that it is now 09:00:02.36, you should be able to round down to determine that this is the 9am alarm.