andygeers
andygeers

Reputation: 6986

Android daily alarm firing too often or only once

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:

  1. The problem lies in AlarmReceiver, like it ought to not be starting up a brand new service but doing something with the existing service
  2. I shouldn't bother excluding null intent values in my onStartCommand function

I'm just a little nervous to try number 2 in case it causes people's devices to annoy them again!

Upvotes: 1

Views: 3717

Answers (2)

MadTracki
MadTracki

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

CommonsWare
CommonsWare

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

Related Questions