In this note, we will discuss scheduling jobs which are not required to be completed immediately, but can be done later when more resources are available.
Currently, Android has two APIs with which we can schedule jobs:
In AlarmManager API, we can schedule a task to be done by the system at a given time in the future. The AlarmManager requires a broadcast receiver to capture the task and perform the required action. We can also schedule repetitive jobs which are to be performed at certain intervals. While we are scheduling an alarm, we must pass the type, the time when the alarm is scheduled, and a PendingIntent.
JobScheduler API was introduced in Lollipop and its the most efficient way to perform background work, especially networking. It performs background work based on conditions, not on time. These conditions may be whether the device is connected to a network, charging or idle.
The JobScheduler API operates at the system level, so it has many features by which it can intelligently schedule and trigger the jobs that have been assigned.
Modern Android applications should use the JobScheduler API. Apps can schedule jobs while letting the system optimize based on memory, power, and connectivity conditions.
The JobScheduler API does not work solely on time scheduled tasks, but checks whether the correct conditions are met before triggering a task. The JobScheduler API runs on the main thread of the application, so it can interact with the app more easily and reduce the chances of being stopped by the system to free memory.
Let's look at advantages of the JobScheduler API. Compared to a custom SyncAdapter or the AlarmManager, the JobScheduler supports batch scheduling of jobs. The Android system can combine jobs so that battery consumption is reduced. JobManager makes handling uploads easier as it handles automatically the unreliability of the network. It also survives application restarts. Here are example when you would use this job scheduler:
The JobScheduler consists of three main sections:
JobInfo
JobService
JobScheduler
Let us look into each one of them in detail.
JobInfo
A unit of work is encapsulated by a JobInfo
object. This object specifies the scheduling criteria. The job scheduler allows to consider the state of the device, e.g., if it is idle or if network is available at the moment. You can schedule the task to run under specific conditions, such as:
These constraints can be combined. For example, you can schedule a job every 20 minutes, whenever the device is connected to an unmetered network. Deadline is a hard constraint, if that expires the job is always scheduled.
So, all the parameters that are to be used while scheduling the Job by the scheduler are defined in the JobInfo
class. We can use JobInfo.Builder
to create an instance of JobInfo
. This builder requires the jobId
and the service component as its parameters. JobId
can be used to uniquely identify jobs that are scheduled to perform a task. They can be also used to monitor whether the job has been successfully called or not by the app when the conditions are met.
Let’s take a moment and talk about the potential criteria you can include in your JobInfo
object.
JobInfo
means the system will assume you do not need any network access and you will not be able to contact your server.onStopJob()
, for example), the system will employ your specified policy over the default.isOverrideDeadlineExpired()
to determine if you’re in that case). And if they are met, but your minimum latency has not elapsed, your job will be held.RECEIVE_BOOT_COMPLETED
permission for this to work, though.)The following are the parameters that can be set using the JobInfo
class:
setBackoffCriteria(long initialBackoffMillis, int backoffPolicy)
. Back-off Criteria is the policy that checks when a job is finished or a retry is requested. You can set the initial back off time and whether it is linear or exponential. The default is 30 sec and exponential. The max back-off capacity is five hrs. Also, setting this method for a job along with setRequiresDeviceIdle(boolean)
will throw an exception when you call build()
, as back off typically does not make sense for these types of jobs. The back off policy can be set one of the two values:JobInfo.BACKOFF_POLICY_LINEAR
will use the same back off time for the next retry attempt.JobInfo.BACKOFF_POLICY_EXPONENTIAL
will retry after exponentially increasing the back off time.setExtras(PersistableBundle extras)
. A Bundle
of extras. This lets you send specific data to your job. As this is persisted, only primitive types are allowed.setMinimumLatency(long minLatencyMillis)
. A minimum amount of time your job should be delayed. Calling this method for a periodic job will throw an exception when you call build()
.setOverrideDeadline(long maxExecutionDelayMillis)
. A maximum amount of time to wait to execute your job. If you hit this time, your job will be executed immediately regardless of your other parameters. Calling this method for a periodic job will throw an
exception when you call build()
.setPeriodic (long intervalMillis)
. If you want the job to be repeated, you can specify the interval between repeats. You are guaranteed to be executed within an interval but cannot guarantee at what point during that interval this willl occur. This can sometimes lead to jobs being run closely together. Setting this function on the builder with setMinimumLatency(long)
or setOverrideDeadline(long)
will result in an error.setPersisted(boolean isPersisted)
. You can persist the job across boot. This requires the RECEIVE_BOOT_COMPLETED
permission to be added to your manifest.setRequiredNetworkType(int networkType)
. The network type you want the device to have when your job is executed. You can choose between:NETWORK_TYPE_NONE
. No network connection is required for the job. This is the default value set for the job.NETWORK_TYPE_ANY
. This specifies that the Job requires an internet connection, but it can be of any type, such as Wi-Fi, mobile network, or any other type of connection.NETWORK_TYPE_UNMETERED
. This is specified when a job has to be triggered when the network type is unmetered, for example, Wi-Fi.NETWORK_TYPE_NOT_ROAMING
. This is added in API 24. It is specified when network should not be roaming to trigger the Job.setRequiresCharging(boolean requiresCharging)
. Whether or not the device should be charging.setRequiresDeviceIdle(boolean requiresDeviceIdle)
. If the device should be idle when running the job. This is a great time to do resource heavy jobs.Here is a code example of how a JobInfo
object can be initialized.
ComponentName serviceComponent = new ComponentName(context, MyJobService.class); JobInfo jobInfo = new JobInfo.Builder(0, serviceComponent) .setBackoffCriteria(30*1000, JobInfo.BACKOFF_POLICY_EXPONENTIAL) .setMinimumLatency(5 * 1000) // wait at least .setOverrideDeadline(10 * 1000) // maximum delay .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED) // require unmetered network .setRequiresDeviceIdle(true) // device should be idle .setRequiresCharging(false) // we don't care if the device is charging or not .build();
Next, we will see about the JobService
.
JobService
The JobService
is the actual service that is going to run our Job. The new JobService
must be registered in the AndroidManifest
with the BIND_JOB_SERVICE permission
. This service has different methods to implement than a normal service:
onStartJob(JobParameters params)
. This method is what gets called when the JobScheduler
decides to run your job based on its parameters. You can get the jobId
from the JobParameters
and you will have to hold on to these parameters to finish the job later. The jobId
can be used to identify each job. The onStartJob
is performed in the main thread, if you start asynchronous processing in this method, return true
otherwise false
.onStopJob(JobParameters params)
. This will get called when your parameters are no longer being met. In our previous example, this would happen when the user switches off of Wi-Fi, unplugs, or turns the screen off on their device. If the job fails for some reason, return true
from on the onStopJob
to restart the job.Here are some important things to know when using a JobService
:
JobService
runs on the main thread. It is your responsibility to move your work off-thread. If the user tries to open your app while a job is running on the main thread they might get an Android Not Responding (ANR) error. This can be done by performing the tasks to be done in the service in a background thread within the service itself, using a Handler or an AsyncTask.JobScheduler
keeps a wake lock for your job. If you don't call jobFinished()
with the JobParameters
from onStartJob()
the JobScheduler
will keep a wake lock for your app and burn the device's battery. Even worse, the battery history will blame your app. So, remember to call jobFinished
when the job has completed its work. jobFinished()
requires two parameters: the current job, so that it knows which wakelock can be released, and a boolean indicating whether you’d like to reschedule the job.The new JobService
must be registered in the AndroidManifest with the BIND_JOB_SERVICE
permission.
<service android:name=".MyJobService" android:permission="android.permission.BIND_JOB_SERVICE"> </service>
Here is a code example to show how the JobService
class looks:
public class MyJobService extends JobService { private static final String TAG = MyJobService.class.getSimpleName(); JobParameters params; @Override public boolean onStartJob(JobParameters params) { Log.d(TAG, "Job Started"); this.params = params; doTask(); // return true if using background thread else return false return true; } @Override public boolean onStopJob(JobParameters params) { Log.d(TAG, "Job Force Stopped"); // reschedule job return true else return false return true; } private void doTask() { try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } jobFinished(params, false); } }
Next, we will see how to schedule the Job using the JobScheduler
class.
JobScheduler
We now have our JobInfo
and our JobService
, so it is time to schedule our job. All we have to do is get the JobService
the same way you would get any system service and hand it our JobInfo
with the schedule(JobInfo job)
method.
Here is a code example showing how the job has to be scheduled:
JobScheduler jobScheduler = (JobScheduler)getSystemService(Context.JOB_SCHEDULER_SERVICE); jobScheduler.schedule(jobInfo);