2

In my app, I have an alarm set that when run, launches a service that will perform a task (in my app, it is sending a text message without user interaction). This message can be set for 2 hours in the future, or a year in the future. Here is how I implement it.

In my main activity

user types message, choses a date and time (translated to milliseconds), and that alarm time (the milliseconds), is saved persistently. I then call a service, which i have set to be sticky, and boot on startup.

In my service.

When the service gets called (Which after the first time you schedule a message, it is now running, all the time), it creates the alarm with the saved alarm time. This is good, because if the phone reboots, the alarm will get recreated again.

Here is the problem

After the alarm time has passed, and the message is successfully sent, I don't see a way to shut down that service from always running. So as soon as the service gets killed and restarted (like when the phone reboots), it launches, sees the alarm time is in the past, and immediately launches the service that sends the message. So until I clear data on the app, I get random duplicate messages sent out, at random future times.

My solution (not elegant).

After the message sends the first time, i add 50 years (in milliseconds), to the alarm time, and resave it persistently. This way it won't trigger again until the user actually sets the time themselves. This seems to have worked, but IMO is a terrible solution.

There has to be a more proper way of setting a persistent alarm to only run once. Can someone who has more experience with AlarmManager advise me?

icodebuster
  • 8,890
  • 7
  • 62
  • 65
BriCo84
  • 881
  • 1
  • 8
  • 10
  • Are you using AlarmManager? Are you clearing your alarm time from your persistent storage after the alarm fires? – Karakuri Jul 11 '13 at 15:41
  • Why can't you cancel the alarm after the message has been sent? – codeMagic Jul 11 '13 at 15:43
  • Yes I am using AlarmManager. It doesn't matter if i clear the alarm time, because if it is zero, the alarm when it gets set again (on boot, and then checks the alarm time for what milliseconds to set it to) will fire right away, as of now, i am adding 50 years in milliseconds to that time, so that it won't fire. I can't cancel the alarm, because it does no good. Remember, in order to set the alarm to not get killed on a reboot, i have to have it launch from a service that starts on boot. so it will always run that code on boot, to create the alarm – BriCo84 Jul 11 '13 at 17:09
  • The alarm is set up to upon launching of the service, which by default launches on bootup. So even if i cancel the alarm after it has fired, the next time the phone reboots, it will start that service, and recreate the alarm. – BriCo84 Jul 11 '13 at 17:32

3 Answers3

0

You can register an BroadcastReciever onBooted to start the Alarm again - but you definitly need to save the alarm-time in sharedpreferences or an sqlite database...

went through the same sooner

Lukas Olsen
  • 5,294
  • 7
  • 22
  • 28
  • Please re-read the question. The issue is not, that it doesn't save on boot up. it does save. I am using a broadcast receiver. – BriCo84 Jul 11 '13 at 15:37
0

You'd be better off doing something like using an AlarmManager to deliver a PendingIntent at your set time which starts an IntentService that delivers your message and terminates when the message is sent. You'd reset the AlarmManager with the PendingIntent in your BroadcastReceiver that listens for BOOT_COMPLETED. It's not good to have your service running all the time, and it might get killed (and making it a foreground service to make it less likely to be killed would be wasteful of resources).

HexAndBugs
  • 5,549
  • 2
  • 27
  • 36
  • Ok, but how do i set up the pending intent to fire at a specific time? Don't i need to set an alarm for that? And if so, if i want it to not die if the phone reboots before it fires, i have to have that service start on bootup right? So I am in the same position as before. – BriCo84 Jul 11 '13 at 17:11
  • You do need an AlarmManager to set the PendingIntent to fire at a specific time, and you reset the AlarmManager with the PendingIntent if the phone reboots before it fires. Your BroadcastReceiver that detects the boot completing can start an IntentService which either sends the message or resets the AlarmManager. An IntentService has the huge advantage that it doesn't keep running the whole time, so is much cleaner than your original solution. Also, as others have stated, you just need a flag to identify whether the message has been sent or not, which you'd check in the IntentService. – HexAndBugs Jul 12 '13 at 05:56
0

When the service gets called (Which after the first time you schedule a message, it is now running, all the time)

Please do not waste the user's memory by having a service running that is not doing anything but watching the clock tick.

it creates the alarm with the saved alarm time

I am assuming, from context, that "creates the alarm" means "schedules an event with AlarmManager". If you are using AlarmManager, there is no reason to have a service that is "always running".

I don't see a way to shut down that service from always running

It should not have been "always running" in the first place. That being said, to stop a service, a service can call stopSelf().

So as soon as the service gets killed and restarted (like when the phone reboots), it launches, sees the alarm time is in the past, and immediately launches the service that sends the message

Mark the message as having been sent in whatever persistent store that you are using for the rest of this.

After the message sends the first time, i add 50 years (in milliseconds), to the alarm time, and resave it persistently. This way it won't trigger again until the user actually sets the time themselves. This seems to have worked, but IMO is a terrible solution.

You are welcome to use a hasBeenDelivered boolean or the equivalent. Or, depending on your data structure, delete the entry entirely. Regardless, your persistent data model needs to reflect the sent/not-sent status of the message, however you choose to do it.

CommonsWare
  • 986,068
  • 189
  • 2,389
  • 2,491
  • Thanks for your answer. I have thought about using a boolean flag for sent/not sent, but i saw it as no different really than what i was already doing. Really, I am just looking at this and thinking, there has to be a more elegant, proper way to do this. In response to always having the service running to create the alarm manager, the way i see it, i have to have it always running and on bootup. because if the app gets killed, or the phone reboots, the AlarmManager alarms get killed for that app, so it needs to restart, and reset the alarm if that happens. Am I missing something? – BriCo84 Jul 11 '13 at 17:13
  • @BriCo84: "Am I missing something?" -- you are mistaken with your statement "the AlarmManager alarms get killed for that app". That only happens if the user force-stops you (e.g., through Settings), in which case you have bigger problems. "there has to be a more elegant, proper way to do this" -- having an accurate data model, indicating the state of events, would seem to be an elegant, proper way to do this. – CommonsWare Jul 11 '13 at 17:21
  • Oh I thought the alarms get killed if the android system kills the app as well, my mistake there. But the reboot problem is really the issue. If the phone reboots before the alarm goes off, it will get killed/cancelled – BriCo84 Jul 11 '13 at 17:31
  • 1
    @BriCo84: "If the phone reboots before the alarm goes off, it will get killed/cancelled" -- absolutely. Which is why your boot `BroadcastReceiver` can start an `IntentService` which can check your persistent data store, see if there are pending message(s), and schedule alarm(s) for them. The `IntentService` gives you a background thread, plus the service goes away as soon as this work completes, so it does not clog up memory. So long as you are keeping your persistent data model up to date regarding new and completed messages, you should be set. – CommonsWare Jul 11 '13 at 17:33
  • Ok, ya that makes sense, and is a little cleaner than the way i am doing it now. So instead of increasing the alarmtime and having the alarm exist, but be irrelevant (50 years in the future), use the boolean to determine whether to even create the alarm. I guess that is the best solution, but I still think there's gotta be (or should be) an easier way to fire off a one time alarm cleanly. – BriCo84 Jul 11 '13 at 17:40
  • @BriCo84: "use the boolean to determine whether to even create the alarm" -- yup. It's basically the equivalent of the is-read flag in an email client or feed reader app, etc. "I guess that is the best solution, but I still think there's gotta be (or should be) an easier way to fire off a one time alarm cleanly" -- if that's insufficiently clean, then you are out of luck. – CommonsWare Jul 11 '13 at 17:45