AlarmManager, BroadcastReceivers, Activities! Oh my!

August 27, 2013

Intro

This article is the result of hours of frustration, research and lack-of-understanding on my part. I went out with the simple goal of implementing a timer function using Android’s AlarmManager class. With ease and efficiency, traits I have come to expect when using the Android APIs, I got it working in under 15 minutes. It was made of three classes, an activity containing the button for scheduling the alarm, a class to handle the AlarmManager interaction, and a BroadcastReceiver to handle the scheduled events. This was working excellently until I began to test this simple app’s robustness in handling odd, but still possible, user-interactions.

Mainly, the alarm failed to trigger when the app was killed (swiped away) from the recent apps list. This was partly baffling because I assumed, rightfully so, that AlarmManager was handling some of the most important scheduled events on the device and that it had to have a way to maintain alarms outside of the life cycle of a given activity.

So here is the basic solution that I found to this simple problem. The example code I am providing is for the following setup: an activity exists that will schedule the AlarmManager event in its onCreate call. This activity will also have a method for canceling scheduled AlarmManager events just for the sake of easy understanding. A BroadcastReceiver will exist that will handle the explicit intent launched from the AlarmManager. Their will be a few modifications to the general AndroidManifest.xml file. The decision making behind of all this will be explained further on.

The Code

Here is the activity:

public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        scheduleAlarm();
    }

    private void scheduleAlarm() {
        /* Request the AlarmManager object */
        AlarmManager manager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);

        /* Create the PendingIntent that will launch the BroadcastReceiver */
        PendingIntent pending = PendingIntent.getBroadcast(this, 0, new Intent(this, AlarmReceiver.class), 0);

        /* Schedule Alarm with and authorize to WakeUp the device during sleep */
        manager.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + 5 * 1000, pending);
    }

    private void cancelAlarm() {
        /* Request the AlarmManager object */
        AlarmManager manager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);

        /* Create the PendingIntent that would have launched the BroadcastReceiver */
        PendingIntent pending = PendingIntent.getBroadcast(this, 0, new Intent(this, AlarmReceiver.class), 0);

        /* Cancel the alarm associated with that PendingIntent */
        manager.cancel(pending);
    }
}

Let’s break it down:

public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        scheduleAlarm();
    }
    ...
}

This is simply boilerplate code to get the activity running in addition to a single custom method call. The method call scheduleAlarm() is what will initiate the AlarmManager event.

Now moving on to the real code behind scheduleAlarm:

AlarmManager manager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);

This requests the AlarmManager service from the Android System. It runs on any application or base context and returns an object to be casted into AlarmManager.

PendingIntent pending = PendingIntent.getBroadcast(this, 0, new Intent(this, AlarmReceiver.class), 0);

This creates a PendingIntent to be triggered by the AlarmManager. It takes any application or base context to create the internal Intent and wrapping PendingIntent. *Note: this launches our custom BroadcastReceiver, AlarmReceiver, explicitly. Because of this, there is less work required in the AndroidManifest.xml file.

manager.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + 5 * 1000, pending);

This schedules the PendingIntent to be launched by the AlarmManager. AlarmManager.RTC_WAKEUP signifies that we are providing a Unix Time long value for the time that the alarm will be triggered. In addition, the WAKEUP portion tells AlarmManager that even if the device is asleep, trigger this event. System.currentTimeMillis() + 5 * 1000 is used to calculate the Unix Time long value for when to trigger the event: the current time + 20 seconds. pending is the PendingIntent that we want AlarmManager to launch.

To quickly get the cancelAlarm out of the way, everything is the same accept for this line:

manager.cancel(pending);

Fortunately, this line is simple. All it does is cancel any events that are set to launch PendingIntents equal to the supplied one (as decided by Intent.filterEquals).

Now we shall setup the BroadcastReceiver to handle this alarm event:

public class AlarmReceiver extends BroadcastReceiver {
    /* Receives scheduled Alarm intents */
    public void onReceive(Context context, Intent intent) {
        /* Show a success toast*/
        Toast.makeText(context, "Howdy partner", Toast.LENGTH_SHORT);
        /* Launch the MainActivity, just for fun */
        context.startActivity(new Intent(context, MainActivity.class));
    }
}

Once again, break it down:

public class AlarmReceiver extends BroadcastReceiver {
    /* Receives scheduled Alarm intents */
    public void onReceive(Context context, Intent intent) {
        ...
    }
}

This code is the basic boilerplate code for any BroadcastReceiver. We create our own class that extends BroadcastReceiver and implements onReceive. onReceive is the meat-and-potatoes of any BroadcastReceiver: it is the code that gets called when the broadcast is received. Any logic run by a BroadcastReceiver should go here.

Toast.makeText(context, "Howdy partner", Toast.LENGTH_SHORT);

This is simply to show that BroadcastReceivers run their code on the Main UI Thread. This is important because you cannot run long operations directly in a BroadcastReceiver, you must use a Handler, AsyncTask or Service instead.

context.startActivity(new Intent(context, MainActivity.class));

This is another for-show feature. Why not just put this Intent to launch MainActivity in a PendingIntent and trigger it from the AlarmManager directly? Why go through the BroadcastReceiver at all? Simply because BroadcastReceivers are made to handle WAKEUP events. If you were to launch the Activity directly, you wouldn’t be guaranteed that it would run if the device was sleeping; with a BroadcastReceiver, you are.

The final item, and main cause of this blog post, is what occurs in the AndroidManifest.xml file. This is what our example one will look like, with the boilerplate application code taken away:

<activity android:name=".MainActivity">
</activity>

<receiver android:name=".AlarmReceiver" android:process=":remote">
</receiver>

For the final time, here follows the breakdown:

<activity android:name=".MainActivity">
</activity>

This is standard procedure for declaring a normal, no intent-flag (eg: not for launching from a homescreen, more for internal activity launching), activity declaration. android:name=".MainActivity" declares the location of MainActivity to be in the root package, as signified by the prefix dot. The fun part is here:

<receiver android:name=".AlarmReceiver" android:process=":remote">
</receiver>

Specifically, this line: android:process=":remote". This part does a little bit of Android magic (real life magic, the kind that has an explanation). This line tells the Android System that this broadcast receiver should not run in the same process as the rest of the activity, it should be unaffiliated process/thread wise. This is insanely important in certain cases!

Consider this:

You create an alarm clock app. The user sets an alarm and the app schedules an AlarmManager event for that time. The user navigates away from the app, and like many irksome people, either decides to use the built in “Recent App History” screen to swipe-away your app, killing it, OR event worse, runs a Task Killer app to free up memory, also killing it. **Normally, without this special line of code, the AlarmManager event would be cancelled!*

Huh? Why would Android remove an event designed for starting killed processes? Because the act of killing your application, through either the Task Killer or Recent History method, is interpreted as the User not wanting your app running, at all.

And that is why they added android:process=":remote"; to tell the Android System that, yes, this event is really, really important and needs to be run. (Ok, so maybe that isn’t why they added it but still… it is an indirect side-affect of what android:process=":remote" does, and an important one at that!).

Wrap Up

And that’s how you create a basic, user-proof, AlarmManager/BroadcastReceiver, scheduled logic flow in Android! Best of luck on your apps and don’t bother commenting with any questions/corrections.

Tl;dr

Use android:process=":remote" in you receiver’s AndroidManifest.xml entry for separating BroadcastReceivers from the rest of your application life cycle when dealing with AlarmManager.

comments powered by Disqus