Search code examples
androidalarmmanagerrepeatingalarmreceiver

Unable to cancel repeating alarm Android


I have read every question there is about Android, AlarmManager and cancelling.

I currently use an Activity starting a receiver through:

long msInterval = 1;
Intent intent = new Intent(this, Updater.class);
intent.setAction("theAction");

PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 12, intent, 0);
Updater.origin = pendingIntent;

AlarmManager alarmManager = (AlarmManager) getSystemService(ALARM_SERVICE);
alarmManager.setRepeating(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + (msInterval), msInterval, pendingIntent);

This starts the receiver Updater one millisecond after this code was called, with request code 12 (chosen arbitrarily, using 0 produces the same incorrect behaviour). It also sets the origin of Updater to the currently scheduled PendingIntent, which is later used to cancel the alarm.

Updater looks like this:

public class Updater extends BroadcastReceiver {

    public static int flaggedClose = 0;
    public static PendingIntent origin;

    @Override
    public void onReceive(Context context, Intent intent) {
        // Do some work
        Log.w("running", "run");

        if (Updater.flaggedClose != 0) {
            if(flaggedClose == 1) {
                Toast.makeText(context, "Finished!", Toast.LENGTH_SHORT).show();
            }
            flaggedClose++; // Only show Toast once
            Log.w("running", "close");

            origin.cancel();
            AlarmManager alarms = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE);
            alarms.cancel(origin);
        }
    }
}

What it does at the moment is just to log the message "run", which is done ~1000 times/s. When the Activity's onStop() is called, Updater.flaggedClose is set to 1. I can be see this in Logcat since it starts printing the log warning "close". However, the alarm is still on, so every other logged message is "run" and every other is "close". In best case, the alarm is closed after a few seconds. Worst case I need to restart the phone. In the description of AlarmManager, it specifically states that close closes "Any alarm, of any type, whose Intent matches this one (as defined by filterEquals(Intent)), will be canceled". Why are there still alarms being triggered?


Solution

  • As ci_ mentioned in the comments, it is possible that "those 100 "extra" alarms already triggered before the cancel happens". For anyone else who has the same problem, here is a solution. I tested the AlarmManager and it seems to work best if you have a delay of at least 200 ms. for a lower delay use a Handler. Example from the question using Handler:

    public class MainActivity extends Activity {
    
        private boolean pressed = false;
        private boolean done = false;
        private Handler worker;
        private Runnable method;
        long msInterval = 1;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            worker = new Handler();
            method = getMethod();
    
            Button toggle = (Button)(findViewById(R.id.toggle));
            toggle.setOnClickListener(new OnClickListener() {
    
                @Override
                public void onClick(View v) {
                    if(!pressed) {
                        worker.post(method);
                        pressed = true;
                    } else {
                        done = true;
                    }
                }
            });
        }
    
        @Override
        protected void onDestroy() {
            super.onDestroy();
            done = true;
        }
    
        private Runnable getMethod() {
            return new Runnable() {
                public void run() {
                    Log.w("running", "run");
                    if (done) {
                        Toast.makeText(getApplicationContext(), "Finished!", Toast.LENGTH_SHORT).show();
                        Log.w("running", "close");
                    } else {
                        worker.postDelayed(method, msInterval);
                    }
                }
            };
        }
    }
    

    On first button press the handler starts the runnable, and on each call the runnable calls itself. On second button press condition done is set to true, thus the runnable finishes after one (cleanup) round.