Reputation: 6986
I'm trying to implement a "daily reminder" function in my Android app, that should fire once per day at a set time. The first implementation I tried worked for most people, but some subset of users (including at least one person running on Android 4.3 on a Samsung) were reporting that the alarm was firing WAY more often than it should, e.g. every 10 minutes, and every time they opened the app, and just generally being very annoying.
Here's how the alarm is enabled:
Intent myIntent = new Intent(ctx, AlarmReceiver.class);
PendingIntent pendingIntent = PendingIntent.getBroadcast(ctx, 0, myIntent,0);
AlarmManager alarmManager = (AlarmManager)ctx.getSystemService(Service.ALARM_SERVICE);
alarmManager.cancel(pendingIntent);
alarmManager.setRepeating(AlarmManager.RTC_WAKEUP, sched,
AlarmManager.INTERVAL_DAY,
pendingIntent);
Then there's this AlarmReceiver class:
public class AlarmReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
// TODO Auto-generated method stub
Intent service1 = new Intent(context, AlarmService.class);
context.startService(service1);
}
}
This is registered as a receiver in the AndroidManifest: <receiver android:name=".AlarmReceiver"/>
Finally there's the AlarmService, which used to look like this:
public class AlarmService extends Service {
@Override
public IBinder onBind(Intent arg0) {
// TODO Auto-generated method stub
return null;
}
@Override
public void onCreate()
{
// TODO Auto-generated method stub
super.onCreate();
}
@SuppressWarnings("static-access")
@Override
public void onStart(Intent intent, int startId)
{
super.onStart(intent, startId);
Log.v("pm", "about to notify");
Intent intent1 = new Intent(this.getApplicationContext(), MainActivity.class);
intent1.setAction(Intent.ACTION_MAIN);
intent1.addCategory(Intent.CATEGORY_LAUNCHER);
//intent1.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP| Intent.FLAG_ACTIVITY_CLEAR_TOP);
PendingIntent pendingNotificationIntent = PendingIntent.getActivity( this.getApplicationContext(),0, intent1,PendingIntent.FLAG_UPDATE_CURRENT);
Notification notification = new Notification.Builder(this.getApplicationContext())
.setContentTitle("My App")
.setContentText("Don't forget that thing!")
.setSmallIcon(R.drawable.ic_launcher)
.setWhen(System.currentTimeMillis())
.setContentIntent(pendingNotificationIntent)
.getNotification();
notification.flags |= Notification.FLAG_AUTO_CANCEL;
notification.defaults |= Notification.DEFAULT_SOUND;
notification.defaults |= Notification.DEFAULT_VIBRATE;
NotificationManager nManager =
(NotificationManager) getSystemService(NOTIFICATION_SERVICE);
nManager.notify(0, notification);
}
@Override
public void onDestroy()
{
// TODO Auto-generated method stub
super.onDestroy();
}
}
However, as I say, people were reporting that this fired every ten minutes or so! So I tried changing the AlarmService to a less deprecated implementation, but in the process now people are saying it only fires once, and then never again!
I replaced onStart
with this:
@Override
public int onStartCommand(Intent intent, int flags, int startId)
{
Log.v("pm", "about to notify");
if (intent != null) {
Intent intent1 = new Intent(this.getApplicationContext(), MainActivity.class);
intent1.setAction(Intent.ACTION_MAIN);
intent1.addCategory(Intent.CATEGORY_LAUNCHER);
//intent1.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP| Intent.FLAG_ACTIVITY_CLEAR_TOP);
PendingIntent pendingNotificationIntent = PendingIntent.getActivity( this.getApplicationContext(),0, intent1,PendingIntent.FLAG_UPDATE_CURRENT);
Notification notification = new Notification.Builder(this.getApplicationContext())
.setContentTitle("My App")
.setContentText("Don't forget that thing!")
.setSmallIcon(R.drawable.ic_launcher)
.setWhen(System.currentTimeMillis())
.setContentIntent(pendingNotificationIntent)
.getNotification();
notification.flags |= Notification.FLAG_AUTO_CANCEL;
notification.defaults |= Notification.DEFAULT_SOUND;
notification.defaults |= Notification.DEFAULT_VIBRATE;
NotificationManager nManager =
(NotificationManager) getSystemService(NOTIFICATION_SERVICE);
nManager.notify(0, notification);
} else {
Log.v("pm", "Null Intent");
}
return START_STICKY;
}
Since I can't reproduce the original issue on my devices, it's a bit hard to test! My two theories are:
intent
values in my onStartCommand
functionI'm just a little nervous to try number 2 in case it causes people's devices to annoy them again!
Upvotes: 1
Views: 3717
Reputation: 21
I just wanted to add for all people who got problems with the AlarmManager and Android 4.4+ that it is truly important that you add the stopSelf();
, like @CommonsWave already said, to the bottom of your onStartCommand() within Service which is called from the BroadcastReceiver
Upvotes: 0
Reputation: 1006964
Here's how the alarm is enabled
Note that this will be inexact on Android 4.4+, even though you are using setRepeating()
, once you raise your android:targetSdkVersion
to 19 or higher.
Then there's this AlarmReceiver class
That will not be reliable with a _WAKEUP
-style alarm. It is eminently possible for the device to fall asleep between the startService()
call and when your service actually gets a chance to do something. Please use WakefulBroadcastReceiver
or my WakefulIntentService
for _WAKEUP
-style alarms if you are going to use the delegate-to-a-service pattern.
but in the process now people are saying it only fires once per day!
Since that is what you want, I would think that this is a good thing.
I replaced onStart with this:
I do not know why you are using a Service
instead IntentService
. Regardless, please call stopSelf()
at the bottom of your onStartCommand()
method, so the service goes away. There is no reason for this service to stay running once this work is completed. Also, replace START_STICKY
with START_NOT_STICKY
.
And, if this is all the work you intend to do in the service, you could dump the service entirely and move your onStartCommand()
guts into onReceive()
of the BroadcastReceiver
.
The pattern of delegating work to a service from a receiver is used when the work will take too long to risk tying up the main application thread (e.g., >1ms)... but then your service needs a background thread, which yours lacks. Since I would expect your code to be less than 1ms in execution time, you could just do that in onReceive()
and simplify your app, you would no longer need the separate Service
, nor any of the Wakeful*
stuff I mentioned earlier.
The problem lies in AlarmReceiver, like it ought to not be starting up a brand new service but doing something with the existing service
If this only runs once per day, there better not be an "existing service". There is no need for you to have a running process, tying up system RAM, just waiting for the clock to tick.
I shouldn't bother excluding null intent values in my onStartCommand function
You will get a null
Intent
if:
Your process was terminated before the service completed onStartCommand()
for a startService()
call, and
Your service had successfully run onStartCommand()
before and returned START_STICKY
I'm more and more wondering why my AlarmReceiver creates a service, rather than just showing the notification directly.
Agreed. If you plan on lots more work, involving disk or network I/O, then use an IntentService
(background thread, service stops itself automatically). Otherwise, I'd just put this in onReceive()
and call it good.
Upvotes: 6