Scheduling operations via Android-Job in Android Android 20.10.2017

Scheduling operations via JobScheduler in Android

In the modern application development, it is very common for our application to perform tasks asynchronously and scope of them are outside the application’s life-cycle like downloading some data or updating network resources. In some situations we also have to do some work but it is not required to do it right now. To schedule background work, Android introduced several APIs which we can use wisely in our applications.

Selecting a proper scheduler can improve application performance and battery life of the device.

There are several approaches to schedule a task:

  • Schedule task in lifetime of your app. When application is running and we want to schedule or run a task at a specific time then it is recommended to use Handler class in conjunction with Timer and Thread. Instead of using Alarm Manger, Job Scheduler etc. it is easier and much more efficient to use Handler. It's preferred for normal timing operations (ticks timeouts etc) and suitable where scheduling needs to happen with an interval less then 1 second. But doesn’t work efficiently while the app is in the background or not running at the moment.
  • Schedule task outside the lifetime of your app. There are several APIs available to schedule tasks in Android outside the lifetime of your app:

Alarm Manager. AlarmManager provides access to system-level alarm services. This give a way to perform any operations outside the lifetime of your application. So you can trigger events or actions even when your application is not running. AlarmManager can startup a service in future. It is used to trigger a PendingIntent when alarm goes off.

Registered alarms are retained while the device is asleep (and can optionally wake the device up if they go off during that time), but will be cleared if it is turned off and rebooted.

We should only use AlarmManager API for tasks that must execute at a specific time. This does not provide more robust execution conditions like device is idle, network is available or charging detect.

Use Case: Let’s say we want to perform a task after 1 hour or every 1 hour. In this case AlarmManager works perfectly for us. But this API is not suitable in a situations like perform the above task only when network is available or when device is not charging.

Pros

  • Available on all devices and all OS version.
  • Easier to send a Broadcast to start a service delayed using this API.

Cons

  • API changes with changing Android API Versions.
  • Periodic Alarms only work in case the interval between consecutive occurrences is greater than 60 seconds.
  • Alarms get reset on device reboot and hence need to be explicitly re-initiated.

Job Scheduler. This is the chief among all the mentioned scheduling options and perform the background work very efficiently. JobScheduler API which was introduced in Android 5.0(API level 21).

This API allows to batch jobs when the device has more resources available or when the right conditions are met. All of the conditions can be defined when you’re creating a job. When the criteria declared are met, the system will execute this job on your application’s JobService. JobScheduler also defers the execution as necessary to comply with Doze mode and App Standby restrictions.

Batching job execution in this fashion allows the device to enter and stay in sleep states longer, preserving battery life. In general this API can be used to schedule everything that is not time critical for the user.

Pros

  • Relatively easier to use and provides customizations for the developer to schedule jobs better.
  • Respects device state and prevents exploitation of resources by apps.

Cons

  • Only available on Android Lollipop and above (21+).
  • Jobs with network dependency don’t get scheduled if the periodic interval less than 30 seconds.
  • Jobs stop getting scheduled in Power-Saver Mode if network connectivity is a dependency.

GCM Network Manager. GCM (Google Cloud Messaging) Network Manager has all the schedule features from JobScheduler. GCM Network Manager is also meant for performing repeated or one-off, non-imminent work while keeping battery life in mind.

It is used to support backward compatibility and can also use below Android 5.0 (API level 21). From API level 23 or higher, GCM Network Manager uses the framework’s JobScheduler. GCM Network Manager uses the scheduling engine inside Google Play services so this class will only work if the Google Play services is installed on the device.

Google has strongly recommended for GCM users to upgrade to FCM and instead use Firebase Job Dispatcher for scheduling any tasks.

Pros

  • Supported on Android Gingerbread and above (9+).

Cons

  • Only available on devices with Google Play Services pre-installed.
  • Jobs with network dependency don’t get scheduled if the periodic interval less than 30 seconds.
  • Jobs stop getting scheduled in Power-Saver Mode if network connectivity is a dependency.

Firebase Job Dispatcher. The Firebase JobDispatcher is also a library for scheduling background jobs. It is also used to support backward compatibility (below API level 21) and works on all recent versions of Android (API level 9+).

This library will also works when running device do not have Google play services installed and wants to schedule a job in the application. In this condition this library internally uses AlarmManager. If Google Play service is available on the device then it uses the scheduling engine inside Google Play services.

Sync Adapter. Sync adapters are designed specifically for syncing data between a device and the cloud. It should be only use for this type of task. Syncs could be triggered from data changes in either the cloud or device, or by elapsed time and time of day. The Android system will try to batch outgoing syncs to save battery life and transfers that are unable to run will queue up for transfer at some later time. The system will attempt syncs only when the device is connected to a network.

Wherever possible, it is advised via Google to use JobScheduler, Firebase JobDispatcher, or GCM Network Manager.

In Android N (API level 24), the SyncManager sits on top of the JobScheduler. You should only use the SyncAdapter class if you require the additional functionality that it provides.

Android-Job. A utility library for Android to run jobs delayed in the background. Depending on the Android version either the JobScheduler, GcmNetworkManager or AlarmManager is getting used.

Smart Scheduler Android. A utility library for Android to schedule one-time or periodic jobs while your app is running.

Setting up and using Android-Job

Include android-job dependency in your app module’s build.gradle file and sync project.

dependencies {
    ...
    compile 'com.evernote:android-job:1.2.0'
}

Implementing Android-Job is super easy.

The API includes below classes/interfaces.

  1. Job. Your jobs need to extend this class and override onRunJob method. The heavy lifting is done here. You must return a Result from this method so that the system knows whether to attempt to run your job at a later time.
  2. JobRequest. You can schedule a Job by creating a JobRequest using its builder constructor and passing your Job tag.
  3. JobCreator. JobCreator acts like a factory to provide a Job based on a job tag. Your concrete JobCreator class must implement the JobCreator interface and override the create() method.
  4. JobManager. The JobManager class serves as the entry point. Before using this class you must initialize this as singleton. JobManager takes a Context. After creating the instance, you have to add your JobCreator to JobManager.

Here we will develop a simple app to show notification to the user periodically.

Let’s create our DoWalkJob class in the first step.

class DoWalkJob extends Job {
    static final String TAG = "job_walk_tag";

    @NonNull
    @Override
    protected Result onRunJob(Params params) {

        PendingIntent pi = PendingIntent.getActivity(getContext(), 0,
                new Intent(getContext(), MainActivity.class), 0);

        Notification notification = new NotificationCompat.Builder(getContext())
                .setContentTitle("Do relax")
                .setContentText("Take a walk.")
                .setAutoCancel(true)
                .setContentIntent(pi)
                .setSmallIcon(R.mipmap.ic_launcher)
                .setShowWhen(true)
                .setColor(Color.RED)
                .setLocalOnly(true)
                .build();

        NotificationManagerCompat.from(getContext())
                .notify(new Random().nextInt(), notification);

        return Result.SUCCESS;
    }

    static void schedulePeriodic() {
        new JobRequest.Builder(DoWalkJob.TAG)
                .setPeriodic(TimeUnit.MINUTES.toMillis(25), TimeUnit.MINUTES.toMillis(20))
                .setUpdateCurrent(true)
                .build()
                .schedule();
    }
}

In the onRunJob method we are simply showing a notification and we are returning Result.SUCCESS every time the job runs.

Also we have a static schedulePeriodic method which builds the JobRequest through its Builder and schedules the job by calling schedule(). We are passing DoWalkJob.TAG in the Builder constructor which tells our JobCreator to return aDoWalkJob`` instance.

JobRequest.Builder has many methods. Here we are calling setPeriodic to tell the system that this is a periodic job which runs every 25 minutes. Also we have passed 20 minutes as the 2nd parameter which is actually the flex time. This helps the system to batch our job with any other jobs with similar time interval.

setUpdateCurrent(true) will update the current job instead of creating a new job.

Next let’s create our MyCreator class which implements JobCreator interface.

class MyCreator implements JobCreator {
    @Override
    public Job create(String tag) {
        switch (tag) {
            case DoWalkJob.TAG:
                return new DoWalkJob();
            default:
                return null;
        }
    }
}

As you can see, in the create() method we have a switch construct which maps a tag with a specific job.

In the next step we will create our MyApplication class which extends Application class. In the MyApplication class we will create the JobManager instance by passing our application context and add our MyCreator to the JobManager.

public class MyApplication extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        JobManager.create(this).addJobCreator(new MyCreator());
    }
}

In the final step we will call the schedulePeriodic method of our DoWalkJob class in the onCreate method of MainActivity to schedule our job when our application starts.

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        DoWalkJob.schedulePeriodic();
    }
}