Xamarin.Android Firebase Job Dispatcher: Background Scheduling for all Android Devices

Tom Opgenorth

Running tasks in the background is important for Android apps. It’s what helps keep an app responsive to the user and allows work to get done even when the user isn’t actively engaged with the app. As an example, consider uploading a large file or trying to apply a filter to a picture. Both of these tasks should be done in the background. To do so otherwise will result in the work blocking the UI thread, making the app unresponsive to the user. If the app is unresponsive for a long enough time, then Android will kill the process with the dreaded “Application Not Responding” message.

However, running tasks in the background is a compromise between ensuring that the work is performed without negatively impacting the usability of the device. For example, background work should not unnecessarily drain the battery by constantly keeping the device awake. Additionally, Android 6.0 and higher impose limitations on what and how that work can be performed in the background. These factors can make background processing in Android a complex scenario.

As an example of this, consider uploading a file to a website. A large file might inflict significant data charges on the user if they are not connected to their home network. Alternatively, a slow network may cause significant battery usage because the network activity is keeping the device in a higher power state for a lengthy period of time. In this scenario, the app should try to schedule the upload when the device is charging and connected to an unmetered (free) network.

Introducing the FirebaseJobDispatcher

The Firebase Job Dispatcher is a library (backward compatible to API level 9) to simplify background processing by helping developers run and write apps that run intelligently and efficiently. The Firebase Job Dispatcher contains APIs that handle these details for the developer:

  • Encapsulating the work in discrete classes for cleaner code.
  • Intelligently scheduling the work to run when certain conditions are met.
  • Rescheduling the work to run if the task fails, or allowing a task to run more than once.

This library runs as a system service, exposed through an object that is an instance of the FirebaseJobDispatcher class. This object will help the app create a job that contains some or all of the following meta-data:

  • JobService is an abstract class that is extended by the app and contains the code for the job.
  • Criteria are the conditions that determine when a job should be run.
  • Triggers identify a time constraint for when the job should be run.
  • Parameters are any inputs that are required to run.

This diagram shows how all of these concepts relate to each other:

Components of the Firebase Job Dispatcher

The Firebase.JobDispatcher binding provides the CreateJobDispatcher extension method that will take any Android.App.Context (such as an Activity) and return a reference to the FirebaseJobDispatcher. This code snippet shows an Activity getting a reference the FirebaseJobDispatcher:

Firebase.JobDispatcher.FirebaseJobDispatcher dispatcher = this.CreateJobDispatcher();

Encapsulating Code in a JobService

The core of a job is the Firebase.JobDispatcher.JobService abstract class. This class is a specialized Android service that encapsulates the work to be performed, and is adorned with an IntentFilter declaring the FirebaseJobServiceIntent.Action. There are two methods that a JobService must override:

  • OnStartJob – Must be overridden and is invoked by the FirebaseJobDispatcher when it is time to run the job.

This method will run on the UI thread of the application. If the work is a small, easy task (less than 16 milliseconds), it may run on the main thread. However, lengthy tasks such as disk access or network calls must run asynchronously. OnStartJob should return “true” if it’s running work on another thread, while “false” should be returned if the all the work was performed within OnStartJob itself.

  • OnStopJob – Must be overridden and is called when the system has to prematurely terminate the job and provides the JobService a chance to perform any necessary cleanup. If the job should be rescheduled, it should return “true”.

The following code shows a sample Firebase Job Dispatcher JobService:

[Service(Name = "FirebaseJobDispatcherSample.FibonacciJob")]
[IntentFilter(new[] { FirebaseJobServiceIntent.Action })]
public class FibonacciJob : Firebase.JobDispatcher.JobService
{
    public override bool OnStartJob(IJobParameters jobParameters)
    {
        // Called by the FirebaseJobDispatcher to start the job.
        // Return TRUE if the job is doing work on another thread.
        return true;
    }

    public override bool OnStopJob(IJobParameters jobParameters)
    {
        // Called by Android when it has to terminate the job.
        // Return true if the job should be rescheduled, false if it shouldn’t.
        return false;
    }
}

All of the information that the FirebaseJobDispatcher requires to run the job, such as what class to use, any parameters that were passed to the job, and the criteria that were used to schedule the job are provided in the IJobParameters parameter.

Instantiating and Running a JobService

The Firebase.JobDispatcher.Job.Builder is used to create a Firebase.JobDispatcher.Job object. A job, at the very minimum, must identify which JobService will perform the work and have a tag, a string that helps identify the instance of the job. After the Job is created, it can be scheduled with the FirebaseJobDispatcher. This code shows one way to instantiate and schedule a Firebase.JobDispatcher.Job:

Firebase.JobDispatcher.Job job = dispatcher.NewJobBuilder()
                                           .SetService("fibnonacci-calculation")
                                           .Build();
int result = dispatcher.Schedule(job);

Parameters can be passed to a job through an Android.App.Bundle object. Building on the previous example, this code will pass the integer value 25 to the job:

int value = Int32.Parse(inputEditText.Text);
Bundle jobParameters = new Bundle();
jobParameters.PutInt(“fibonacci_value_key”, value);

var job = dispatcher.NewJobBuilder()
                    .SetService("fibnonacci-calculation")
                    .SetExtras(jobParameters)
                    .Build();

int result = dispatcher.Schedule(job);

The JobService can access this parameter by reading the value from the IJobParameters object that is passed in via OnStartJob:

public override bool OnStartJob(IJobParameters jobParameters)
{
    Int fibonacciValue = jobParameters.Extras.GetInt(FirebaseJobDispatcherHelpers.FibonacciValueKey, -1);

    // Calculate the Fibonacci value for the value provided (on a background thread)

    return true;
}

For the curious, this next step shows how to set a trigger to run the job in the next 1-5 seconds once an unmetered network is connected to the device:

int value = Int32.Parse(inputEditText.Text);

Bundle jobParameters = new Bundle();
jobParameters.PutInt(FirebaseJobDispatcherHelpers.FibonacciValueKey, value);

// This job should run between 1 - 5 seconds after being scheduled.
JobTrigger.ExecutionWindowTrigger trigger = Trigger.ExecutionWindow(1, 5);

var job = dispatcher.NewJobBuilder()
                    .SetService("fibnonacci-calculation")
                    .SetExtras(jobParameters)
                    .SetTrigger(trigger)
                    .SetConstraints(Constraint.OnUnmeteredNetwork)
                    .Build();

int result = dispatcher.Schedule(job);

Finishing the Work

Finally, when a JobService has finished its work, it must call its .JobFinished() method. This method will signal to the FirebaseJobDispatcher that work is done and can now be cleaned up. This diagram shows how all these methods relate together:

JobService methods

To perform background work and support older versions of Android, the Firebase Job Dispatcher is the library for you and it will help proof your future apps for Android 8.0 and higher. Try out a sample in your apps today!

Disucss this post on the Xamarin forums!

0 comments

Discussion is closed.

Feedback usabilla icon