Background Processing in Android

Amir Ansari
8 min readOct 5, 2020

--

Background processing means something happening in the background and not literally visible to user.
Any task that takes more than a few milliseconds should be delegated to a background thread.
To keep our app responsive all time is an important part while developing an Android application, .

Making a network request on the main thread causes the thread to wait, or block, until it receives a response.
Since the thread is blocked, the OS can’t call onDraw(), and our app freezes.
It can lead into an Application Not Responding (ANR) dialog

An app is considered to be in the foreground if -
1. The app has a visible activity/fragment
2. The app has a foreground service
3. Another foreground app is connected to the app, either by binding to one of its services, or using one of its content providers
For ex. IME, Wallpaper service, Voice service

If none of the above conditions is true, the app is considered to be in the background.

Examples of task which should be done in background thread-
1. decoding a bitmap
2. download audio
3. network requests
4. accessing storage
5. working on a machine learning model

Background tasks fall into one of the following main categories:
1. Immediate — task need to complete while the user is interacting with the application
2. Deferred — some tasks are postponed on constraints such as network availability and remaining battery or storage is not low or device screen is not OFF etc.
Most background tasks come in this category.
ex. Upload logs to your server, Encrypt/Decrypt content, sync the content
3. Exact — task need to run at an exact time
ex. Alarm clock, Medicine reminder, Notification about a TV show

Kotlin coroutines are preferred for Immediate tasks, but we will check them in another post.

presently, recommended solution for deferred tasks is WorkManager.
It guarantee task execution even if the app exits or the device restarts

We have several ways to do background jobs -

  1. Executing tasks at an exact point of time by AlarmManager.

Code example -
//getting the alarm manager
AlarmManager am = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
/creating a new intent specifying the broadcast receiver
Intent i = new Intent(this, MyAlarm.class);
//creating a pending intent using the intent
PendingIntent pi = PendingIntent.getBroadcast(this, 0, i, 0);
//setting the repeating alarm that will be fired every day
am.setRepeating(AlarmManager.RTC, time, AlarmManager.INTERVAL_DAY, pi);

If you want to schedule tasks other than a time constraint then you should use JobScheduler.

For tasks which should execute only when the app is open or in the app’s process, we can use thread pool.

2. Loaders
We use loaders mostly when the background task is loading information that will only be used in the activity.
Loader are tied to the activity lifecycle
Used in scanrios where UI thread can wait, Ex. populate a recyclerView from a database

3. Services
when there is no UI required for a long running task
Ex: Updating a database in the background.
they may block our UI if we directly run a long running task since Service operate on main thread.
To get around this, we need to create a background thread or AsyncTask from within our service.
Unless you are not using Service as Foreground service — there is no good reason for using service presently.

Foreground service should be used when user has to notify the performing action,
for example to play an audio track. Foreground services must display a Notification.
Foreground services continue running even when the user isn’t interacting with the app.
If the app needs to complete a user-initiated task without deferring even if the user leaves the app or turns off the screen,
such as in the case of music/video playback or navigation, you should use a Foreground service.

4. IntentService
The Service runs in background but it runs on the Main Thread of the application, While IntentService runs on a separate worker thread.
All IntentService requests are handles in a single background thread, and they are issued in order.

An IntentService has a few limitations:
It can’t interact directly with our user interface. To put its results in the UI, we have to send them to an Activity.

Also, Work requests run sequentially. If an operation is running in an IntentService, and we send it another request, the request waits until the first operation is finished.
An operation running on an IntentService can’t be interrupted.

It looks similar to Service declaration.
<service
android:name=”.DownloadService”
android:exported=”false”/>

but <service> element doesn’t contain an intent filter.

The Activity that sends work requests to the service uses an explicit Intent, so no filter is needed.
This also means that only components in the same app or other applications with the same user ID can access the service.

Q. When do we use IntentService?

A. When we want to perform multiple background tasks one by one

An IntentService invoked multiple times won’t create multiple instances but a service do.
Google recently deprecated IntentService.

Now, Think about a login scenario. A user fills in email and password, and clicks on login button. Sadly, User’s network has poor data connection and the user walks in the elevator. So login request is stuck as no sever access.

Usually, we put default three network retries, each of 30 seconds.
Therefore, In worst case scenario: we can wait for work to complete in 90 seconds.
This may not happen and even user’s phone can go offline and lose internet connectivity.
From a user perspective if he already inserted email and password and clicked on sign-in button. Even asked again to do so, a very bad user experience for him.
It is a technical issue and we can’t switch to Service to handle this problem, because Service is not started such way.

So here JobScheduler comes to the rescue.

5. JobScheduler was introduced on Android Lollipop, compatible till API 21

A little code -

JobScheduler mJobScheduler = (JobScheduler)getSystemService(Context.JOB_SCHEDULER_SERVICE);
JobInfo.Builder builder = new JobInfo.Builder(jobId, serviceComponent)
.setRequiredNetworkType(jobInfoNetworkType)
.setRequiresCharging(false)
.setRequiresDeviceIdle(false)
.setExtras(extras).build();
mJobScheduler.schedule(jobInfo);

It’s scheduled a Job to start. When the right time comes — the system will start our MyJobService
It is supported from API 21.
However, when device restart, it does not work. this bug was fixed in API23

FirebaseJobDispatcher is another way which has compatibility till GB API 9 but it need google play service.
So minSDK < 23, means use FirebaseJobDispatcher, which require Google Play Services so big markets not supporting it for ex. China, cannot run our app.

**************************

Constraints in background tasks-

background tasks may degrade the Battery life or result in poor device performance because of RAM consumption
To improve battery life and give a better user experience, Android has imposed some limits on background execution.

Doze: If a user unplugs a device and leaves it stationary, with its screen off, for a period of time, the device goes into Doze mode
In this mode, devices periodically resume normal operations for brief periods of time for pending operations or syncing

App Standby: when the user does not touch the app for a certain period of time.
If the device is unplugged, the system disables network access and suspends syncs and operations for the apps

App standby buckets: The system limits apps’ access to device resources like the CPU or battery, based on the user’s usage patterns.
Apps are in 5 buckets, Active, working, Frequent, Rare, Never
This is a new feature for Android 9.

Wi-Fi Manager: The startScan() method performs a full scan for background apps only a few times each hour.
If a background app calls the method again soon afterward, the WifiManager class provides cached results from the previous scan.

Location Manager: location updates are provided to background apps only a few times each hour.

The system doesn’t allow a background app to create a background service.
Android 8.0 introduces the new method startForegroundService() to start a new service in the foreground.

After the system has created the service, the app has five seconds to call the service’s startForeground() method to show the new service’s user-visible notification.
If the app does not call startForeground() within the time limit, the system stops the service and declares the app to be ANR.

Android 8.0 or higher apps can no longer register broadcast receivers for implicit broadcasts in their manifest.
An implicit broadcast is a broadcast that does not target that app specifically.
For example, ACTION_PACKAGE_REPLACED is an implicit broadcast, since it is sent to all registered listeners,
However, Apps can use Context.registerReceiver() at runtime to register a receiver for any broadcasts.

Broadcasts that require a signature permission are exempted from this restriction, since these broadcasts are only sent to apps that are signed with the same certificate
A number of implicit broadcasts are currently exempted from this limitation.
ex. ACTION_BOOT_COMPLETED, ACTION_LOCALE_CHANGED, ACTION_MEDIA_MOUNTED

Beginning in Android 9 (API level 28), if an app exhibits some of the bad behaviors described in Android vitals,
the system prompts the user to restrict that app’s access to system resources.

Considering above constraints, comes modern solution which handles them nicely.

Let’s see more on WorkManager.

6. WorkManager is Google’s answer to Evernote’s AndroidJob
Improvement include, oberving job status, chaining of job, we can define constraints, also No Google Play required.

WorkManager uses JobScheduler service to schedule the jobs.
If JobScheduler is not supported by the device, then it uses Firebase JobDispatcher service.
If Firebase JobDispatcher is not available on the device, it will use AlarmManager and BroadcastReceiver.
So it allows backward compatibility.

WorkManager can provide a signal to the OS that the process should be kept alive if possible while this work is executing.
These Workers can run longer than 10 minutes.
WorkManager ensures tasks finish. so 100% guarantee.

With WorkManager, we can schedule both periodic tasks and complex dependent chains of tasks:
background work can be executed in parallel or sequentially,
where we can specify an execution order.

We can set criteria on when the background task should run.
For example, there’s no reason to make an HTTP request to a remote server if the device doesn’t have a network connection.
So we can set a Constraint that the task can only run when a network connection is present.

It is compatible with different OS versions.
for API23+ it determine background scheduler as JobScheduler or for lower it user AlarmManager and BroadcastReceicer.

OneTimeWorkRequest — for tasks that are to be executed once

val uploadWorkRequest = OneTimeWorkRequestBuilder<UserDataUploadWorker>().build()
WorkManager.getInstance().enqueue(uploadWorkRequest)

PeriodicWorkRequest — The work keeps on executing periodically until it is cancelled.
The minimum repeat interval is 15 minutes
The work cannot be chained with other work requests
A PeriodicWorkRequest can be executed as shown below:

val periodicWorkRequest = PeriodicWorkRequestBuilder<UserDataUploadWorker>(24, TimeUnit.HOURS).build()
WorkManager.getInstance().enqueue(periodicWorkRequest)

The code above creates a periodic work request which is executed every 24 hours.
In order to stop the execution of periodic work, it needs to be explicitly cancelled:

WorkManager.getInstance().cancelWorkById(periodicWorkRequest.id)

Result class is used to return the status of our worker task
doWork() method of the Worker class to do the background tasks
Work Result can be of three types:
1. Result.success()
2. Result.failure() — we should return Result.failure() only when our chain of workers is dependent on this worker’s output
3. Result.retry() — This result indicates that the work is terminated for some reason and that it should be retried

WorkManager is not answer to all of the background tasks.
Ex. We shouldn’t use it for processing payments since it doesn’t need to survive process death and these task needs to be executed immediately.
limitation- can’t enqueue chain of periodic and one-time work together.

That’s all for this post.
Please read and create with the help of Google codelab for first project using WorkManager.

Have fun.

--

--