In today’s tutorial we’re going to take a look on how to handle periodically scheduled tasks on our Android device by using BroadcastReceivers, Services and the AlarmManager.
Prerequisites
The following environment is needed to follow this tutorial …
-
I recommend using Eclipse with the ADT Plugin installed
The Concept
We’re going to create a first broadcast receiver that gets initiated at boot time. This component then should make use of Android’s AlarmManager API and schedule a cyclic task using an explicit intent.
A second broadcast receiver handles this intent and starts the final service.
The first Receiver
The following receiver instantiates the AlarmManager and instructs him to send off an intent every 20 seconds.
package com.hascode.android;
import java.util.Calendar;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
public class SchedulerSetupReceiver extends BroadcastReceiver {
private static final String APP_TAG = "com.hascode.android";
private static final int EXEC_INTERVAL = 20 * 1000;
@Override
public void onReceive(final Context ctx, final Intent intent) {
Log.d(APP_TAG, "SchedulerSetupReceiver.onReceive() called");
AlarmManager alarmManager = (AlarmManager) ctx
.getSystemService(Context.ALARM_SERVICE);
Intent i = new Intent(ctx, SchedulerEventReceiver.class); // explicit
// intent
PendingIntent intentExecuted = PendingIntent.getBroadcast(ctx, 0, i,
PendingIntent.FLAG_CANCEL_CURRENT);
Calendar now = Calendar.getInstance();
now.add(Calendar.SECOND, 20);
alarmManager.setRepeating(AlarmManager.RTC_WAKEUP,
now.getTimeInMillis(), EXEC_INTERVAL, intentExecuted);
}
}
This is the receiver’s declaration in our AndroidManifest.xml. Please note that we’re filtering for two intents: action.BOOT_COMPLETED and action.USER_PRESET and we’re forcing the receiver to run in a specific process: “:hascode_process“. The following receiver and services are also running in this process.
<receiver android:name="SchedulerSetupReceiver" android:process=":hascode_process">
<intent-filter >
<action android:name="android.intent.action.BOOT_COMPLETED" />
<action android:name="android.intent.action.USER_PRESENT" />
</intent-filter>
</receiver>
Catching the Intent
Now we’re creating a second broadcast receiver to catch our explicit intent fired from the AlarmManager.
This class does not do much here – it’s just starting the final class – our service
package com.hascode.android;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
public class SchedulerEventReceiver extends BroadcastReceiver {
private static final String APP_TAG = "com.hascode.android";
@Override
public void onReceive(final Context ctx, final Intent intent) {
Log.d(APP_TAG, "SchedulerEventReceiver.onReceive() called");
Intent eventService = new Intent(ctx, SchedulerEventService.class);
ctx.startService(eventService);
}
}
That’s how the receiver is declared in our AndroidManifest.xml
<receiver android:name="SchedulerEventReceiver" android:process=":hascode_process"/>
Creating the Service
And last but not least the service is executed. There is only one specialty here .. we’re using onStartCommand instead of onStart and we’re returning Service.START_NOT_STICKY. First of all – onStart is deprecated in Android 2.0 and has no return value.
The return value of onStartCommand allows us to specify what to do when our Service is killed. In our case, START_NOT_STICKY tells the operating system that it may forget about the service if it has to kill it .. in our scenario this is not really a problem because the next tick from the AlarmManager will be creating a new instance (indirectly) anyway …
package com.hascode.android;
import java.util.Date;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.util.Log;
public class SchedulerEventService extends Service {
private static final String APP_TAG = "com.hascode.android.scheduler";
@Override
public IBinder onBind(final Intent intent) {
return null;
}
@Override
public int onStartCommand(final Intent intent, final int flags,
final int startId) {
Log.d(APP_TAG, "event received in service: " + new Date().toString());
return Service.START_NOT_STICKY;
}
}
And the service’s declaration in the manifest
<service android:name="SchedulerEventService" android:process=":hascode_process"/>
Just to be sure – here is my complete AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest package="com.hascode.android"
android:versionCode="1"
android:versionName="1.0" xmlns:android="http://schemas.android.com/apk/res/android">
<uses-sdk android:minSdkVersion="8" />
<application
android:debuggable="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name">
<service android:name="SchedulerEventService" android:process=":hascode_process">
</service>
<receiver android:name="SchedulerEventReceiver" android:process=":hascode_process">
</receiver>
<receiver android:name="SchedulerSetupReceiver" android:process=":hascode_process">
<intent-filter >
<action android:name="android.intent.action.BOOT_COMPLETED" />
<action android:name="android.intent.action.USER_PRESENT" />
</intent-filter>
</receiver>
</application>
</manifest>
Running the Service
If you’re running the project on your device or using the emulator you should be able to see an output similar to this using logcat or DDMS
Tutorial Sources
I have put the source from this tutorial on my GitHub repository – download it there or check it out using Git:
git clone https://github.com/hascode/android-scheduling-tutorial.git