Preparing for O: The Death of Background Services

header

Confused about what exactly is changing for background services? We were too. Hopefully this will help clear up some of your concerns and help their heads around the changes required for background processing with Android O.

For this post, we are going to focus solely on the background service execution limits. There are also other limits that Android will start to enforce when you target Android O, but those will be saved for other posts down the road!


When Google started releasing the changes in Android O, the biggest one that stuck out to me was the death of background services. It is pretty common, in Android, to handle background tasks by scheduling an IntentService to run at a specified time through the AlarmManager.

With Android O, you will NOT be able to do this!

One of the major changes for O is that Google is placing background execution limits on apps. This could definitely lead to a huge improvement in battery life but it will be a bit of a change for apps that use lots of background processes for loading data or performing calculations.

When will apps be able to run in the background?

Starting with Android O, if your app is not in the foreground - being displayed to the user - the only way to forcefully run a background job is through a Firebase Cloud Messaging high priority data notification. So, if your app uses push notifications, it will still be able to handle that new data, in real time. This is a great way for Google to start opening developers up to the push model, rather than the normal pull model.

At Klinker Apps, we have started to move to this push model as much as we can already and have had great success with it. Firebase, as a platform, is an amazing tool for developers and they make it so easy to implement push notifications. Pulse SMS uses Firebase Cloud Messaging to do all its device-to-device communication, and we have another project in the works that takes full advantage of this as well (more on that later!).

While push notifications are a great tool, they aren't for everyone or every implementation. When Android Lollipop was released, Google added a JobScheduler, that is meant to act as an alternative (now a replacement) for scheduling tasks through the AlarmManager. JobScheduler is smarter about when jobs should be run and can batch them together so that devices stay asleep as much as possible.

Push notifications are a great tool, but they aren't for everyone, or every implementation.

Android's JobScheduler is a powerful API that allows you to schedule tasks based on way more than just run-time. For this post though, we just want to think about how it can be used to replace the timed-based scheduling of the AlarmManager.

If your phone has tons of background jobs that are all meant to run at about the same time, JobScheduler will start all of those jobs in a batch, instead of individually, to cut down the number of times the device has to be woken up. This is the battery saving that Google is looking to improve on, and why they are requiring developers to start using the JobScheduler instead of the AlarmManager.

We won't go any deeper in to the push notifications side of things, right now, but read on to see how to start converting your current IntentService background tasks, to run with the JobScheduler.

Converting Scheduled IntentServices to JobServices

Now that we understand the shift to JobScheduler, how do we actually make our apps take advantage of that?

Let's say you have a basic IntentService, scheduled with the AlarmManager, for one minute in the future:

public class TestIntentService extends IntentService {  
    private static final int REQUEST_ID = 1;
    private static final int ONE_MIN = 60 * 1000;

    public static void schedule(Context context) {
        Intent intent = new Intent(context, TestIntentService.class);
        PendingIntent pIntent = PendingIntent.getService(context, REQUEST_ID,
                intent, PendingIntent.FLAG_UPDATE_CURRENT);

        AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
        alarmManager.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + ONE_MIN, pIntent);
    }

    @Override
    protected void onHandleIntent(Intent intent) {
        doMyWork();
    }

    private void doMyWork() {
        // do my work here!
    }
}

It is really easy to convert this into a JobService:

public class TestJobService extends JobService {  
    private static final int JOB_ID = 1;
    private static final int ONE_MIN = 60 * 1000;

    public static void schedule(Context context) {
        ComponentName component = new ComponentName(context, TestJobService.class);
        JobInfo.Builder builder = new JobInfo.Builder(JOB_ID, component)
                // schedule it to run any time between 1 - 5 minutes
                .setMinimumLatency(ONE_MIN)
                .setOverrideDeadline(5 * ONE_MIN);

        JobScheduler jobScheduler = (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE);
        jobScheduler.schedule(builder.build());
    }

    @Override
    public boolean onStartJob(JobParameters params) {
        doMyWork();
        return true;
    }

    @Override
    public boolean onStopJob(JobParameters params) {
        // whether or not you would like JobScheduler to automatically retry your failed job.
        return false;
    }

    private void doMyWork() {
        // I am on the main thread, so if you need to do background work,
        // be sure to start up an AsyncTask, Thread, or IntentService!
    }
}

Great! One last code change, the entry in our AndroidManifest.xml for the new JobService should look like this:

<service android:name=".TestJobService"  
         android:permission="android.permission.BIND_JOB_SERVICE" />

Obviously, the change to JobScheduler, ends up being a pretty simple one, and I like it's API much more than the AlarmManager API.

For the observant ones: you probably noticed my comment in the TestJobService#doMyWork method. By default, JobService extends the normal Android Service class, so it does all its work on the main thread, and not a background thread, like IntentService does! This is a really important point, especially if you are doing any networking. You will need to push it to a background thread.

Support for Android 4.4 and Below

kitkat

JobScheduler was only introduced in Android Lollipop (5.0), so what if your app has a minimum SDK that is less than 21? Are you just out of luck?

Of course not! Google's backward compatible version of the JobScheduler is firebase-jobdispatcher. It has an extremely similar API to the JobScheduler.

Be aware, jobdispatcher does require Google Play Services. This means if you aren't already including that in your project, it will inflate your APK size a bit.

If you have an app that supports anything under Lollipop - as most apps do - I highly recommend using it to support JobScheduler all the way back SDK 9. If this isn't a requirement for you, go for the native JobScheduler instead, since you won't have to rely on Google Play Services.

Foreground service support

foreground services Image Credit: Android Police

With Android O, there are also changes to foreground services. Foreground services are just services that create a persistent notification that Android knows not to kill in low memory situations.

Before, apps would start a normal Service, then promote it to a foreground service with the Service#startForeground method. In O, if any Service is marked as a foreground service, then it will keep your app in the foreground and it will allow you to start any background tasks that you want.

This may or may not be the route you want to take, obviously this will keep the Service alive, but it also adds an annoying (?) notification to the notification panel (although this has been improved in Android O). This may be perfect for some apps, but for others that just need to run quick background jobs, this isn't really ideal.

One last thing to note on foreground services - there is a change in the way you should launch them when targeting SDK 26. The legacy workflow of starting a service then promoting it to the foreground isn't going to work if your app is already in the background, since it will not be able to start, due to the new background execution limits. Google has introduced a new NotificationManager#startServiceInForeground method to handle creating a foreground service from the start, instead of being forced to promote it.


Hopefully that cleared up some confusion about what is coming in Android O, what you will need to change, and one way that you can make that conversion. This post really just goes into detail about converting simple scheduled background jobs, but that just scratches the surface of what JobScheduler can do for you.

If you have been holding out on using this API, try it out! Before long, it will be required.