For the More Curious: JobScheduler and JobServices

In this chapter, you saw how to use AlarmManager, an IntentService, and PendingIntents to put together a periodically executing background task. In doing that, you had to do a few things manually:

  • schedule a periodic task

  • check whether that periodic task was currently running

  • check whether the network was currently up

You had to stitch a few different APIs together by hand to create one functioning background worker that you could start and stop. It worked, but it was a lot of work.

In Android Lollipop (API 21), a new API was introduced called JobScheduler that can implement these kinds of tasks on its own. It is also capable of more: For example, it can avoid starting up your service at all if the network is unavailable. It can implement a back off and retry policy if your request fails or restrict network access on a metered connection. You can also restrict updates so that they only occur while the device is charging. Even where these are possible with AlarmManager and IntentService, it is not easy.

JobScheduler allows you to define services to run particular jobs and then schedule them to run only when particular conditions apply. Here is how it works: First, you create a subclass of JobService to handle your job. A JobService has two methods to override: onStartJob(JobParameters) and onStopJob(JobParameters). (Do not enter this code anywhere. It is only a sample for purposes of this discussion.)

public class PollService extends JobService {
    @Override
    public boolean onStartJob(JobParameters params) {
        return false;
    }

    @Override
    public boolean onStopJob(JobParameters params) {
        return false;
    }
}

When Android is ready to run your job, your service will be started and you will receive a call to onStartJob(…) on your main thread. Returning false from this method means, I went ahead and did everything this job needs, so it is complete. Returning true means, Got it. I am working on this job now, but I am not done yet.

IntentService created a background thread for you, so you did not have to worry about threading. In JobService, though, you must implement your own threading. You might do that with an AsyncTask:

private PollTask mCurrentTask;

@Override
public boolean onStartJob(JobParameters params) {
    mCurrentTask = new PollTask();
    mCurrentTask.execute(params);
    return true;
}

private class PollTask extends AsyncTask<JobParameters,Void,Void> {
    @Override
    protected Void doInBackground(JobParameters... params) {
        JobParameters jobParams = params[0];

        // Poll Flickr for new images

        jobFinished(jobParams, false);
        return null;
    }
}

When you are done with your job, you call jobFinished(JobParameters, boolean) to say that you are done. Passing in true for the second parameter means that you were not able to get the job done this time and it should be rescheduled for the future.

While your job is running, you may receive a call to the onStopJob(JobParameters) callback. This means that your job needs to be interrupted. This can happen when, for example, you only want your job to run when a WiFi connection is available. If the phone moves out of WiFi range while your job is still running, you will get a call to onStopJob(…), which is your cue to drop everything immediately.

@Override
public boolean onStopJob(JobParameters params) {
    if (mCurrentTask != null) {
        mCurrentTask.cancel(true);
    }
    return true;
}

When onStopJob(…) is called, your service is about to be shut down. No waiting is allowed: You must stop your work immediately. Returning true here means that your job should be rescheduled to run again in the future. Returning false means, Okay, I was done anyway. Do not reschedule me.

When you register your service in the manifest, you must export it and add a permission:

<service
    android:name=".PollService"
    android:permission="android.permission.BIND_JOB_SERVICE"
    android:exported="true"/>

Exporting it exposes it to the world at large, but adding the permission restricts it back down so that only JobScheduler can run it.

Once you have created a JobService, kicking it off is a snap. You can use JobScheduler to check on whether your job has been scheduled.

final int JOB_ID = 1;

JobScheduler scheduler = (JobScheduler)
    context.getSystemService(Context.JOB_SCHEDULER_SERVICE);

boolean hasBeenScheduled = false;
for (JobInfo jobInfo : scheduler.getAllPendingJobs()) {
    if (jobInfo.getId() == JOB_ID) {
        hasBeenScheduled = true;
    }
}

If your job has not been scheduled, you can create a new JobInfo that says when you want your job to run. Hmm, when should PollService run? How about something like this:

final int JOB_ID = 1;

JobScheduler scheduler = (JobScheduler)
    context.getSystemService(Context.JOB_SCHEDULER_SERVICE);

JobInfo jobInfo = new JobInfo.Builder(
        JOB_ID, new ComponentName(context, PollService.class))
        .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED)
        .setPeriodic(1000 * 60 * 15)
        .setPersisted(true)
        .build();
scheduler.schedule(jobInfo);

This schedules your job to run every 15 minutes, but only on WiFi or another unmetered network. Calling setPersisted(true) also makes your job persisted so that it will survive a reboot. Check out the reference documentation to see all the other ways you can configure a JobInfo.

JobScheduler and the future of background work

In this chapter, we showed you how to implement background work without JobScheduler. Because JobScheduler is only available on Lollipop and later, and there is no support library version available, the AlarmManager-based solution shown in this chapter is the only approach from the standard libraries that will work across all the versions of Android that PhotoGallery supports.

It is important to note, though, that AlarmManager’s days of performing this kind of work are probably numbered. One of the highest priority goals for Android platform engineers in recent years has been improving power efficiency. To do this, they have sought greater control on scheduling when apps use the radio, WiFi, and other tools that can run through a battery quickly.

This is why AlarmManager’s commands have changed meaning over the years: Android knows that developers use AlarmManager to schedule background work, so those APIs have been loosened and tweaked to try to make your app play nicely with others.

At its heart, though, AlarmManager is a bad API for this purpose. It tells Android nothing about what you are doing – you could be using the GPS radio or you could be updating the look of an app widget on your user’s Home screen. Android does not know, so it must treat all alarms identically. That keeps Android from making intelligent choices about power consumption.

This pressure means that as soon as JobScheduler is an option for most apps, AlarmManager will fall out of favor. So while JobScheduler is not a compatible option today, we strongly recommend switching your applications to use it as soon as you determine it is feasible.

If you want to do something today instead of planning an API switch in the future, you can use a third-party compatibility library instead. As of this writing, Evernote’s android-job library is the best option. You can find it at github.com/​evernote/​android-job.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.147.104.120