JobIntentService for background processing on Android O

Chang W. Doh
Jun 29, 2017 · 12 min read

One of Android’s greatest strengths is its ability to use system resources in the background regardless app execution. However, sometimes it became the behavior to use system resources excessively.

Past few years, Google has been working on a number of tasks to reduce system resource consumption on Android. Main changes such as Doze and App Standby are to limit resource consumptions in the background which are not recognized by the user. In addition to these changes, lots of indicators in setting is giving users usage of system resource in detail.

Image for post
Image for post
Two levels of Doze (Source: Android Developer Site ) included in Android Nougat

There are a number of changes that are made to Android O, but the biggest part of it is the execution limits and location restrictions that have already been announced.

Let's take a look at background execution limits.

In this article, we will look into limitations of the background service and limitations of implicit broadcast intents .

Background service limitations

The background services can run at any time, but there is a problem that users find it too hard to recognize it. From a user’s point of view, this can be an issue because apps are able to be constantly using resources even when users are not using the app.

Image for post
Image for post
When you do googling, you can see that a lot of people are really interested in battery consumption.

On Android O, a background service that will run a few minutes after the app enters the background, and will automatically stop and onDestroy() will be called. In addition, a call to startService() to run the service in the background state is not allowed because an IllegalStateException is thrown.

Implicit broadcast intent limitations

Implicit Broadcast Intent is a broadcast intent that does not target specific apps. This is problematic because it doesn’t have the specific target for delivery. It’s like calling everyone in your neighborhood at midnight to find your friends.

Image for post
Image for post
"Excuse me. Will you move the car, please? if it’s yours."- Implicit Broadcast is never such a gentle situation.

These implicit broadcast intents can wake up any Receiver or Service that describes it in AndroidManifest.xml. An inactive application component must be loaded and executed for processing, which can be problematic because it is a resource-intensive operation.

In Android N, Google introduced the ability to limit the behavior of certain intents to reduce this. This year's Android O limits the use of resources for more aggressive restrictions that prevent calling the implicit broadcast recipients which registered in the app manifest.

TargetSDK ≥ 26

These background execution limits apply when the target SDK is Android O (API Level 26) or later. If you target Android N or pre-N, you can run the service without limitations. And sometimes it can be relied on app setting, even though your application has been built by under the pre-O SDK.

Nonetheless available features

I mentioned that the background service throws an exception when the application is in the background, and some implicit broadcast intents are not received in the manner that describes intent filters in AndroidManifest.xml. If so, there is a question to be asked.

There are still many things available.

Running the foreground service

Foreground services are available without restrictions. However, it is not possible to set it up as a foreground service because the service can not be started in the background state in the first place.

It can be solved using Context.startForegroundService()API added to 26. This API allows services to start unrestricted, but it also stops the service immediately if you don’t bind to the Notification with Service.startForeground() within 5 seconds.

Image for post
Image for post
Media play, download and upload are examples of typical foreground services.

If users are already familiar with existing foreground service behavior, or if it is reasonable to pass it through notification as appropriate, foreground service on Android O can be a good choice.

Implicit Broadcast Exceptions

Fortunately, not all implicit broadcast intents are restricted, and many essential intents can be used in the same way as before. Obviously, you can register an explicit broadcast in AndroidManifest.xml.

Implicit broadcasts through dynamically registered receivers

You can still use Context.registerReceiver()to to register the receiver dynamically, and the registered receivers can receive implicit broadcast intent without any restriction.

Background behavior through JobScheduler

There is still a shortage of background behavior between the limitations and allowable features described above. This is because we can not completely rule out scenarios like when you need to sync contacts or photo data in the background. In such cases, JobScheduler can be good alternative for the scenarios.

What is JobScheduler?

Android Lollipop introduced JobScheduler as a part of optimizing background behavior. This task required conditions and factors ( JobInfo) and the operation of the condition ( JobService) running in the background. It will be executed at the appropriate time by the Android framework.

Image for post
Image for post
Using the Android Job Scheduler — Google Developers

For example, to sync large data, you should receive the ACTION_POWER_CONNECTED broadcast intent and continuously check the network status or storage. If you use JobScheduler, these conditions will be described on a JobInfo object.

The job implementation consists of the following three steps.

  1. JobInfo: Sets the conditions under which the job will run, JobSchedulerand registers it in the system via.
  2. JobService: implements the necessary action when the job is executed.
  3. android.permission.BIND_JOB_SERVICE: Grants permission of your sub-class of JobService for job execution .

Setting and registration of execution condition

JobInfo manages the conditions such as the network connection types, charge status, device idle time, etc. for deciding a good time to execute JobService. From the article “Scheduling jobs like a pro with JobScheduler”, you can set these conditions as follows:

  • Connected network type
  • Charging status
  • Device idle state
  • Updating the content provider
  • Clip data
  • Execution cycle
  • Minimum latency
  • Deadline setting
  • Retry Policy
  • Whether the current condition is maintained when rebooting

You can describe your JobInfo with a single condition, or define more complex job with several conditions.

Processing jobs

Real works for Job will be written as a sub-class of JobService. JobService has an easy structure that can handle the operation start, finish and notifying the end of the job by calling at any time if necessary. It provides two callbacks and one method:

onStartJob()

This callback is called by the system at the time the job starts. However, since JobService runs on the main thread, we will consider Thread or AsyncTask as needed.

  • onStartJob()Return true if it need more time to finish your work such as AsyncTask or Thread thing, and false if the action is finished at this method.
  • If you return true, you should explicitly notify the end of the job by invoking finishJob(). In this case, the system can call onStopJob() as needed to interrupt the work before the finish of the job.

onStopJob()

Callback invoked by the system if it needs to be canceled before the job is finished. Call to this method doesn’t mean your work is failed, so you have to check progress of works to resume and complete it quickly at next chance.

  • By returning true, you can re-schedule the job that are currently running. it will be run on the next chance with little more latency.
  • Alternatively, return false to prevent this job from being scheduled again.

jobFinished()

Called when all the work on the job is completed and inform the JobManager you’ve finished executing.

Granting a permission to run JobService

Grants android.permission.BIND_JOB_SERVICEpermission to execute the service in AndroidManifest.xml.

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

General Considerations

JobScheduler allows you to use background executions with less impact on system resources such as battery or memory, but this is not all. Before you use it, keep the followings in mind.

  • By default, JobService runs on the main thread.
  • id is a important key for identifying your job.
  • Changing any of the execution conditions would stop current Job.
  • Job execution shares their life with Wakelock.

The most critical issue is backwards compatibility

Of course, implementing a background service using JobService is a very nice approach. However, JobService is a feature supported by API level 21, so there is a backward compatibility issue.

In particular, if target SDK of app addresses O(26) on Android O device, executing service in background wouldn’t be allowed. To clear this limitation, we should apply Job things. However, JobScheduler was introduced at API 21. So, we have to write a backward compatible code for devices not having JobScheduler.

Now, you can understand that we need an alternative way to support Android O and pre-O versions with same code which supports normal background service on pre-O and also supports JobScheduler on O and later.

Let’s take a look JobIntentService.

SupportLib: JobIntentService

Thankfully, Support Library addedJobIntentService for more convenient approach to this. It uses the JobScheduler function on Android O, but runs as a background service to emulate JobScheduler on pre-O. The JobIntentService has the following characteristics:

  • On Android N and below devices, the background service starts immediately and the loaded jobs are executed sequentially. This ensures backwards compatibility with the same code.
  • In Android O and later, jobs loaded through JobScheduler are executed sequentially. Internally, it is scheduled byjobInfo.setOverrideDeadline(0).build(). If you want other conditions, JobIntentService doesn’t fit it.
  • You don’t need WakefulBroadcastReceiver to use Wakelock when running on pre-O, because JobIntentService would manage Wakelock for you. This prevent writing wrong code such as battery drain due to not releasing Wakelocks.
Image for post
Image for post
Background Check and Other Insights into the Android Operating System Framework — Google I / O ‘17

Applying JobIntentService to your project

Gradle

To use JobIntentService, you must include the following maven repository URL in build.gradle:

allprojects { 
repositories {
jcenter ()
maven {
url "https://maven.google.com"
}
}
}

In addition, add the support-compat in dependencies. (The latest version is 26.0.0-beta2 as of June 25, 2017. Please see here for the latest version .)

dependencies { 
...
compile 'com.android.support:support-compat:26.0.0-beta2'
}

JobIntentService is similar to IntentService in the part where reserved works by enqueueWork() are sequentially processed at onHandleWork(). However, it should be noted that onStopCurrentWork() is called at any time when the system needs to stop the job.

Structure of JobIntentService

JobIntentService also takes a simple structure that provides two callbacks and one method:

EnqueueWork (Context context, Class cls, int jobId, Intent work)

Added the work to be executed into the queue.

  • On Android O and later, the deadline is set to 0, but it can be started at any time, due to issues such as doze state or running out of memory.
  • On pre-O, the background service will start by calling to startService(). Since it operates irrespective of the doze, the network timeout caused by Doze should be managed by yourself.
  • All jobIds targeted to the same class (cls) must all have the same value. If you want to assign a different jobId, you need to define it as a new class. If you do not follow this rule, you will not see any problem on pre-O, but Android O and later will throw IllegalArgumentException .
  • Of course, the work argument can not be null. If null, an IllegalArgumentException is thrown.
@RequiresApi (26) 
static final class JobWorkEnqueuer extends JobIntentService.WorkEnqueuer {
private final JobInfo mJobInfo;
private final JobScheduler mJobScheduler;
JobWorkEnqueuer (Context context, Class cls, int jobId) {
...
JobInfo.Builder b =
new JobInfo.Builder(jobId, mComponentName);
mJobInfo = b.setOverrideDeadline(0).build();
mJobScheduler =
(JobScheduler)context.getApplicationContext ()
.GetSystemService (Context.JOB_SCHEDULER_SERVICE);
}
...
}

OnHandleWork (Intent intent)

A method that receives dequeue work from the queue.

  • The intents loaded by enqueueWork() are passed to this method when the service is executed. Like IntentService, loaded intents are delivered sequentially.
  • Wakelock is automatically maintained from the first intent to the completion of the final intent, so you only have to manage the actual work.
  • When onHandleWork() is completed, the next pending work that is loaded from the work queue is dispatched.
  • This method is called in the background thread, so blocking operations would be great for this, but it can be stopped by JobScheduler on Android O and later.
final class CommandProcessor extends AsyncTask <Void, Void, Void> { 
@Override
protected Void doInBackground (Void ... params) {
GenericWorkItem work;
if (DEBUG) Log.d(TAG, "Starting to dequeue work"); While ((work = dequeueWork ())! = Null) {
if (DEBUG) Log.d(TAG, "Processing next work:" + work);
OnHandleWork (work.getIntent ());
if (DEBUG) Log.d(TAG, "Completing work:" + work);
Work.complete ();
}
if (DEBUG) Log.d(TAG, "Done processing work!"); return null;
}
...
}

OnStopCurrentWork ()

Called when the current job must be stopped.

  • As with JobService.onStopJob (), this is called on system shutdown of the job, and you must stop the work and store current states for resuming them at the next time.
  • Return true if you need to re-schedule works. If you do not need to schedule again, you can terminate the job by returning false.

Let’s create a simple example that download the album cover via JobIntentService.

Code for JobIntentService

Other options

In addition to Job, there are so many suitable options. See “ Background Check and Other Insights into the Android Operating System Framework “ at Google I/O 2017.

Image for post
Image for post
Background Check and Other Insights into the Android Operating System Framework — Flowchart

Defer your works as long as possible and do them at the last minute

In fact, whether you use JobScheduler or not, the important thing is that it is not a good user experience if user recognizes how much battery or memory your app consuming even though user doesn’t execute app. If possible, defer your background works and do them at the last and last minute. JobIntentService can be a good starting point for easy implementation.

TIL: Kotlin in Practice

The Publication for Kotlin, focused on Language & Android

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch

Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore

Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store