Why repeating tasks with alarm manager are not accurate on android? - android

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

Related

PendingIntent with IntentService not working

Following code works perfectly for Activity:
Intent intent = new Intent(context, MyActivity.class);
PendingIntent operation = PendingIntent.getActivity(context,
0,
intent,
PendingIntent.FLAG_UPDATE_CURRENT);
alarmmanager.setExact(AlarmManager.RTC_WAKEUP,
startTime.getTimeInMillis(),
operation);
However, when I do the same thing for IntentService, It works only if startTime and time I set alarm are on the same day. e.g. If I set the alarm today for 5 PM, it will be executed but when I set the alarm today for 5 PM tomorrow, it will not be executed. If this was Activity then it works for both cases.
Intent intent = new Intent(context, MyService.class);
PendingIntent operation = PendingIntent.getService(context,
0,
intent,
PendingIntent.FLAG_UPDATE_CURRENT);
alarmmanager.setExact(AlarmManager.RTC_WAKEUP,
startTime.getTimeInMillis(),
operation);
How to solve this?
The goal here I am trying to achieve is to execute IntentService every day at the exact time.
The goal here I am trying to achieve is to execute IntentService every day at the exact time.
Google has made this progressively harder from release to release. See Android AlarmManager setExact() is not exact. There could be two ways to solve this for your case:
you start an activity, which starts the service (as starting an Activity seems to work for you)
you use either setExactAnd... or setAlarmClock. setAlarmClock also triggers in the new "doze" mode, see https://stackoverflow.com/a/47049705/1587329.
Another way would be to re-think why and if you really need this... or if a JobScheduler could not fit your purpose more easily.
add replace your line with this line :
alarmmanager.setRepeating(AlarmManager.RTC_WAKEUP,
startTime.getTimeInMillis(),
operation);
it will repeat on specific interval you set in alarm manager
Replace the AlarmManager with this code:
alarmManager.setRepeating(AlarmManager.RTC,
timeMills,
AlarmManager.INTERVAL_DAY,
pendingIntent);
Worked for me.
HERE IS a DETAILED ANSWER check link in the bottom for more details.
Hope this will help. Your issue can be probably related to android versions too so do check the link for more details
app gets an instance of the AlarmManager and sets an alarm using a PendingIntent. More on usage and setting alarms is coming in the next section. The AlarmManager is the app side interface to the backing AlarmManagerService. It abstracts the details of the Binder interface, used to communicate with the system process (system_server) hosting the AlarmManagerService. These two components manage the alarm(s) the app has set and will send the PendingIntent correctly. This manager/service architecture is used throughout Android framework and is done for security and isolation purposes. The system_server process is running with privileges which normal apps do not have. If you are unfamiliar with Android’s use of permissions, see this article for more details on app processes and user IDs. These extra permissions are what allows system_server to access the underlying kernel alarm driver. The alarm driver is what manages setting alarms to wake up the device regardless of the sleep state of the SoC.
When the alarm is triggered the device is awakened (if asleep) and the AlarmManagerService is notified of an alarm expiring. It will then send the PendingIntent accordingly. This will cause the appropriate component within MyApp to be activated. If MyApp has not been started or its process is not cached, it will be started so the component can be activated.
basic usage will be as
public class MyActivity extends Activity {
...
private AlarmManager mAlarmMgr;
...
public void onCreate(Bundle savedInstance) {
...
mAlarmMgr = (AlarmManager)getSystemService(Context.ALARM_SERVICE);
...
}
...
}
let’s create a PendingIntent for our MyActivity using the component name.
Intent alarmIntent = new Intent(context, MyActivity.class);
PendingIntent pend = PendingIntent.getActivity(context,
0,
alarmIntent,
PendingIntent.FLAG_UPDATE_CURRENT);
Now that we have our PendingIntent and the AlarmManager, we can set our alarm so our Activity is triggered when the alarm has expired. To do this, we need to figure out when we want our alarm to go off and whether it should wake up the device or just be delivered the next time the device is awakened. Remember, we have two different ways of specifying time for our alarms: elapsed time or calendar (RTC) time. So our options are ELAPSED_REALTIME, ELAPSED_REALTIME_WAKEUP, RTC or RTC_WAKEUP. The _WAKEUP variants are our “aggressive” alarms where we want the device to come out of low power to call our app back. For our sample app, let’s set this up in a custom BroadcastReceiver and have it trigger our Activity about 30 seconds after the device is booted
public class MyBootReceiver extends BroadcastReceiver {
public void onReceive(Context, context, Intent intent) {
...
AlarmManager alarmMgr =
(AlarmManager)context.getSystemService(Context.ALARM_SERVICE);
long wakeTime = SystemClock.elapsedRealtime() + 30000;
alarmMgr.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, wakeTime, pend);
}
}
Now when our device boots and the BOOT_COMPLETED broadcast is sent, our app’s process will be started and our receiver will set an alarm to trigger our Activity to be launched about 30 seconds later. Note that on Android 3.1 devices or newer, you must first manually launch your app before the BOOT_COMPLETED.
CREDIT GOES to writer of this BLOG
if you want to set the repeated alarm using SetExact you are bound to stop all other pending intents on the same time check this link for that here are many examples of how to do it! again credit goes to this writer

Android AlarmManager is not triggering alarm on next day when idle

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
);
}

Android setting multiple long date alarms

I'm trying to create a function in my App, which notifies the user at the expiration day of his rented books. I'll work with checkboxes in a listview, as below:
(Dates are for show purposes only)
Now i'm wondering how can i do it the best way. I'm having experiences with AlarmManager and BroadcastReceivers, but I didn't get a clear flowchart yet.
Thats because I need to set an specific alarm to each book and cancel that specific alarm when requested. Also, it needs to reactivate all Alarms when device is restared (by calling BOOT_COMPLETE broadcast).
PS.: Alarms will usually be set to one week after current date.
PS2.: Can I use Calendar to do it? I mean, this way i wouldn't have to reactivate all alarms, or calculate (expirationDate - currentDate) in millis.
Can someone, who has an idea, try to show me the way? Thanks!
I think the key would be to give each and every book its own alarm id as soon as you set the alarm for this book for the first time.
Then you should keep a list of the running alarm ids and timestamps (maybe in SharedPreferences).
With a method like this you can cancel a specific alarm with regards to its alarm id:
public static void cancelAlarm(Context context, int alarmId) {
PendingIntent pi = PendingIntent.getService(context, alarmId,
new Intent(context, YourService.class),
PendingIntent.FLAG_NO_CREATE);
if(pi!=null) {
AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
am.cancel(pi);
}
}
When you receive the BOOT broadcast, you can get the list of alarm ids together with timestamps from SharedPreferences and start all the alarms with their respective alarm ids

Schedule AlarmManager event to happen a few days from now

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.

Best way to set multiple alarms when some are of repeating in nature in Android

I'm building a reminders application where one time, weekly, monthly reminders and we notify the user of the reminder on due date and time. Reminders can be updated any time by the user to update the reminding time or delete the reminder altogether. I have thought of two ways I can solve the particular problem.
Whenever user sets a reminder, schedule an alarm accordingly with an unique Id and update or delete it in case user updates or deletes the alarm.
Since I store the reminding time in DB, better approach would be to schedule an alarm for the nearest reminder. And have the Service which is triggered by the alarm schedule a new alarm for the next nearest reminder.
2nd approach seems clean approach but how do we tackle the case where the Service triggered by alarm gets killed by the system before it schedules a new alarm for the next reminder?
Edit
Looks like if the system kills a Service for memory, it will re-create the Service. Does it mean it is safe to rely on the Service to schedule alarm every time it is run?
Edit 2
I've realized that Android kills any alarms whenever the device is restarted. This makes approach 2 a better solution. I've implemented it for now.
The pending intent needs to be created exactly in the same way for canceling the already set alarm. You can cancel previous alarm when you set a new alarm, if you want to have a single alarm activated at some time.
For setting alarm use:
Intent myIntent = new Intent(getApplicationContext(), SessionReceiver.class);
PendingIntent pendingIntent = PendingIntent.getBroadcast(getApplicationContext(), 1, myIntent, PendingIntent.FLAG_UPDATE_CURRENT);
AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
alarmManager.set(AlarmManager.RTC, now.getTimeInMillis(), pendingIntent);
For canceling the alarm use:
AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
Intent myIntent = new Intent(getApplicationContext(), SessionReceiver.class);
PendingIntent pendingIntent = PendingIntent.getBroadcast(getApplicationContext(), 1, myIntent, PendingIntent.FLAG_UPDATE_CURRENT);
alarmManager.cancel(pendingIntent);

Categories

Resources