Replacing Services With JobScheduler in Android Oreo 8.0

Tom Opgenorth

Before Android 8.0, Android apps would start a service that ran almost indefinitely, even when the app was in the background. This service can be handy for the app, and easy for the developer to implement, but may also have an impact on device experience.

Imagine a scenario where many applications want to perform work when a device is plugged in to charge. When this happens, Android dispatches the android.intent.action.ACTION_POWER_CONNECTED intent. All apps registered to respond to that intent will start up, demanding RAM, CPU time, and bandwidth. Consequentially, it is possible these simultaneous demands may exceed the available space on the device and cause it to slow down. At this point, all a user knows is that they plugged in their device and it started exhibiting sluggish, unresponsive behavior and may assume something is wrong with their phone or memory.

Android and the JobScheduler

Android 8.0 has introduced new background execution limits that dramatically change how services work. When applications move into the background, any services they start will be granted several minutes to complete their work before the operating system terminates them. The Android Framework JobScheduler is one possible way to address this change for Xamarin.Android apps targeting Android 5.0 (API level 21) or higher.

The Android Framework JobScheduler is an API designed to run jobs—discrete, distinct units of work—in the background of various apps. The JobScheduler schedules which jobs will run at appropriate times according to conditions that can be set by the application. Going back to our original example above, the JobScheduler may schedule the jobs so that they run one after another, instead of all at once.

Scheduling

The ability to schedule and queue jobs make the JobScheduler perfect for tasks that are traditionally handled by long-running services, such as:

  • Downloading a file, but only when the device is connected to an unmetered (free) network.
  • When charging a device, make a series of thumbnail images from a collection of larger images.
  • If connected to the network, call a method on a web service using an exponential backoff algorithm between unsuccessful web service calls.

Classes

There are three important classes in the JobScheduler API:

  • JobScheduler: A system service that will run jobs scheduled by apps.
  • JobService: A class extended by applications and contains the code that runs as part of a job. The code for each job is contained in a class that extends the JobService class and requests the android.permission.BIND_JOB_SERVICE permission.
  • JobInfo: Holds information about the job that the JobScheduler needs in order to run a JobService. The JobInfo will tell the JobScheduler which JobService type to use and which conditions must be met before the job can run. It also contains any parameters that the app must pass to the JobService. A JobInfo object is not directly instantiated, instead it is created by using a JobInfo.Builder.

Methods

A JobService sub-class must override two methods:

  • OnStartJob: A system invokes when the job starts and runs on the main thread of the app. If the work is a small, easy task (less than 16 milliseconds), it will run on the main thread. Lengthy tasks such as disk access or network calls must run asynchronously. OnStartJob should return "true" if it is running work on another thread, while "false" should be returned if the all the work was performed within OnStartJob itself.
  •  OnStopJob: Called when the system has to prematurely terminate the job and provide the JobService a chance to perform any necessary cleanup. If the job should be rescheduled, it will return "true".

Applying JobScheduler

The following code shows a sample JobService type:

[Service(Name = "JobScheduleSample.FibonacciJob", Permission = "android.permission.BIND_JOB_SERVICE")]
public class FibonacciJob : JobService
{
   public override bool OnStartJob(JobParameters jobParams)
   {
      // Called by the operating system when starting the service.
      // Start up a thread, do work on the thread.
      return true;
   }

   public override bool OnStopJob(JobParameters jobParams)
   {
      // Called by Android when it has to terminate a running service.
      return false; // Don't reschedule the job.
   }
}

Create a JobInfo Object

In order to schedule a job, it’s necessary for an app to use a JobInfo.JobBuilder to create a JobInfo object. The JobInfo.JobBuilder has a fluent interface and is used to collect meta-data, such as the type of JobService to instantiate and any conditions that should be met before the job is run. This snippet shows how to create a JobInfo class to run the FibonacciJob (from the example above), but only when the device is connected to an “unmetered” (free) network. The job should run between one and five seconds from being scheduled:

Java.Lang.Class javaClass = Java.Lang.Class.FromType(typeof(FibonacciJob);
ComponentName component = new ComponentName(context, javaClass);

JobInfo.Builder builder = new JobInfo.Builder(context, component)
                                     .SetMinimumLatency(1000)   // Wait at least 1 second
                                     .SetOverrideDeadline(5000) // But no longer than 5 seconds
                                     .SetRequiredNetworkType(NetworkType.Unmetered);
JobInfo jobInfo = builder.Build();

In the previous example, the context is any Android Context, such as an Activity. The parameter is a unique integer that identifies the job service to the JobScheduler.

The following C# extension method should help with creating a ComponentName for a given JobService subclass:

public static ComponentName GetComponentNameForJob(this Context context) where T : JobService, new()
{
   Class javaClass = Class.FromType(typeof(T));
   return new ComponentName(context, javaClass);
}

Schedule a Job

Once a JobInfo created is created, the app can schedule a job. This code snippet will get a reference to the JobScheduler service and ask it to schedule the job identified in the jobInfo object:

JobScheduler jobScheduler = (JobScheduler)GetSystemService(JobSchedulerService);
int result = jobScheduler.Schedule(jobInfo);
if (result == JobScheduler.ResultSuccess)
{
   // The job was scheduled.
}
else
{
   // Couldn't schedule the job.
}

JobParameters and .SetExtras

It’s also possible for the app to pass parameters to a service by packaging them up in a Bundle and then calling the JobInfo.SetExtras method. The Bundle is included with the JobParameters object passed to the JobService when OnStartJob is invoked. For example, this snippet will pass a value to a job:

// Bundle up parameters
var jobParameters = new PersistableBundle();
extras.PutLong("some_long_value", 10L);

// Put the Bundle with the parameters into the jobInfo.
JobInfo.Builder builder = new JobInfo.Builder(context, component)
                                     .SetExtras(extras);
JobInfo jobInfo = builder.Build();

To access this value, the JobService should read the .Extras property on the JobParameter object:

public override bool OnStartJob(JobParameters jobParams)
{
   long theValue = jobParams.Extras.GetLong("some_long_value", -1);

   // Job does something with the value.
}

JobFinished

Finally, when a JobService has finished its work (regardless of which thread the work is running on), it should call its own JobFinished() method. It’s important to call this method because it tells the JobScheduler that the work is done and it’s safe to release any wake locks that were acquired for the JobService in the first place. This diagram illustrates how the JobService methods relate to each other and how they would be used:

The relationship of JobService method calls.

Wrapping Up

Now that you’ve seen the basics of the JobScheduler API, try using it in your Xamarin.Android application to replace a task done in a background service. This is a great opportunity to enhance the experience your user has with your applications and update to Android Oreo 8.0 all at once! You can find a simple sample on GitHub that shows the JobScheduler API in action. The sample calculates a Fibonacci number in a job and then passes that number back to an Activity.

Discuss this post in the forums.

0 comments

Discussion is closed.

Feedback usabilla icon