Chapters

Hide chapters

Kotlin Coroutines by Tutorials

Second Edition · Android 10 · Kotlin 1.3 · Android Studio 3.5

Section I: Introduction to Coroutines

Section 1: 9 chapters
Show chapters Hide chapters

16. Android Concurrency Before Coroutines
Written by Nishant Srivastava

The importance of concurrency was discovered quite early on by people who started with Android development. Android is inherently asynchronous and event-driven, with strict requirements as to which threads certain things can happen. Add to this the often-cumbersome Java callback interfaces, and you will be trapped in spaghetti code pretty quickly (aptly termed as “Callback Hell”). No matter how many coding patterns you use to avoid it, you will encounter state changes across multiple threads in one way or the other.

The only way to create a responsive app is by leaving the UI thread as free as possible, letting all the hard work be done asynchronously by background threads.

Note: You’ve already met the term “Callback Hell” in the first chapter. It’s the situation in which you have to serially execute and process the results of asynchronous services by nesting callbacks, often several layers deep.

The purpose of coroutines is to take care of the complications of working with asynchronous programming. You write code sequentially, like you usually would, and then leave the hard asynchronous work up to coroutines.

Using coroutines in Android provides some of the following benefits:

  • Coroutines are a feature provided by a Kotlin library and, thus, can be updated independently from the Android platform releases.

  • Coroutines make asynchronous code look synchronous, making the code more readable. Also, since a synchronous sequence of steps is much easier to manage than asynchronous code, coroutines enable greater confidence in changing the flow when needed.

  • Thanks to coroutines, getting rid of any callbacks and the need to pass around state information is fairly easy, i.e., storing temporary state in a Presenter/ViewModel is simplified. State is not passed across multiple methods any longer.

  • Coroutines are a language feature provided out of the box by Kotlin and, thus, they can be updated independently from the Android platform releases.

  • Coroutines enable better, concise and testable code.

In this chapter, you’ll learn about what different mechanisms already exist for asynchronous programming on the Android platform and why coroutines are a much better replacement for all of them. You’ll see what Kotlin coroutines bring to the table and how they simplify various facets of Android development.

Getting started

Async Wars
Async Wars

For this chapter, you will use a basic app called Async Wars to learn about various async primitives in Android and coroutines at a high level. If you have already downloaded the starter project, then import it into Android Studio.

The project consists of some pre-written utility classes under the package utils. Let’s go over them one by one:

  1. DownloaderUtil: A singleton which has a method called downloadImage() that fetches an image from a pre-setup URL returning a Bitmap. This is done on the main thread and it will be your goal to execute this method on a background thread, and then you will display the image on the screen.
  2. ImageDownloadListener: Interface which is used as a listener for images being downloaded.
  3. BroadcasterUtil: A singleton which is used to abstract away the calls made using LocalBroadcastManager.
  4. MyBroadcastReceiver: Implementation of BroadcastReceiver class used as an adapter between the sender and an ImageDownloadListener.
  5. Extensions.kt: Utility Kotlin extension methods.

Under the package async, you will find GetImageAsyncTask and MyIntentService classes, which will be used and discussed at a later stage in this chapter.

Apart from that, there is MainActivity class where everything is wired up for making calls to download images using various async constructs in Android and to display them in the UI. Almost all the code is pre-written to make it easier for you to switch between these async constructs and see the results. There are two important sections inside MainActivity class that you should take note of:

  1. MethodToDownloadImage: This is an enum class defined inside the MainActivity class, which enumerates all the various types of async construct types in Android.
  2. Inside the onCreate() is a code region marked to be modified:
//region
val doProcessingOnUiThread = true
val methodToUse = MethodToDownloadImage.Thread
//endregion

This is where you will make the changes to trigger the right kind of async construct for downloading an image and displaying it in the UI. Here, when working with async constructs, you will have to set doProcessingOnUiThread = false. After that, the value of methodToUse, which will be one of the items from the MethodToDownloadImage enum class, will be used later to trigger the specific async method.

When not dealing with async constructs, simply set back to doProcessingOnUiThread = true.

Run the app. You will see a UI like below with a button and an animating spinner. The spinner is there to show the impact of calls on the UI thread while a widget is animating. The button will trigger a calculation of a Fibonacci sequence number on the main thread when the flag doProcessingOnUiThread is set to true.

Starter Project
Starter Project

Does Android need coroutines?

When you start an Android application, the first thread spawned by its process is the main thread, also known as the UI thread. This is the most important thread of an application. It is responsible for handling all the user interface logic, user interaction and also tying the application’s moving parts together.

Android takes this very seriously; if your UI thread is stuck working on a task for more than a few seconds, the Android framework will throw an Application Not Responding (ANR) error and the app will crash. Most importantly, even small work on the UI/Main thread can lead to your UI freezing, i.e., animations will stop, and the UI will become non-responsive to the user interaction; everything will stop until the work is finished.

To demonstrate this behavior, inside the MainActivity.kt of the starter app, make sure that the value of the flag doProcessingOnUiThread is set to true. If it is, then simply run the app.

You will see the below app state:

UI blocking processing
UI blocking processing

Now, click the Start button in the UI. This will trigger a call to runUiBlockingProcessing() method. Here is the method definition:

fun runUiBlockingProcessing() {
  // Processing
  showToast("Result: ${fibonacci(40)}")
}

Here, fibonacci(number) method is a helper method and has the below naive implementation:

// ----------- Helper Methods -----------//
fun fibonacci(number: Int): Long {
  return if (number == 1 || number == 2) {
    1
  } else fibonacci(number - 1) + fibonacci(number - 2)
}

Here, the runUiBlockingProcessing() method starts a calculation of the 40th Fibonacci sequence number. Since the processing is done on the UI thread, you will see that the animating spinner stops until the calculation has completed.

You will see a toast message with the result value when the calculation completes, after which the spinner start animating again.

UI blocking processing
UI blocking processing

Now, here is the problem: almost all code in an Android application will be executed on the UI thread by default. Since the tasks on a thread are executed sequentially, this means that your user interface could become unresponsive while it is processing some other work.

Long-running tasks called on the UI thread could be fatal to your application, leading to an ANR dialog, which allows the user to force-quit the application. Even small tasks can compromise the user experience; hence, the correct approach is to move as much work off the UI thread onto a background thread.

Android comes with some pre-built solutions to handle such situations, but, due to its design, it has proven to be difficult for many. Using the low-level threading packages with Android means that you have to worry about a lot of tricky synchronization to avoid race conditions or, worse, deadlocks.

The good news is that the folks working on the Android framework noticed this and provided a better API to deal with such situations. AsyncTask, IntentService, ExecutorService, etc. are some of the very useful classes, as well as the HaMeR classes: Handler, Message and Runnable. Each comes with its own pros and cons.

Take a quick look at each one of them.

Note: Before you continue with the chapter, from here onwards, inside the MainActivity.kt of the starter app, ensure that the value of the flag doProcessingOnUiThread is set to false. You will not be needed to set it to true anymore.

Threads

A thread is an independent path of execution within a program. Every thread in Java is created and controlled by a java.lang.Thread instance. A Java program can have many threads, and these threads can run concurrently, either asynchronously or synchronously.

Every Android developer, at one point or another, needs to deal with threads in their application. The main thread is responsible for dispatching events to the appropriate user-interface widget, as well as communicating with components from the Android UI toolkit. To keep your application responsive, it is essential to avoid using the main thread to perform operations that may last for long.

Network operations and database calls, as well as the loading of certain components, are common examples of operations that should not run in the main thread. When they are called in the main thread, they are called synchronously, which means that the UI will remain completely unresponsive until the operation completes.

For this reason, they are usually performed in separate threads, which thereby avoids blocking the UI while they are being performed (i.e., they run asynchronously from the UI).

Sample usage

You can create a thread in two ways:

  1. Extending the Thread class:
// Creation
class MyThread : Thread() {

  override fun run() {
    doSomeWork()
  }
}

// Usage
val thread = MyThread()
thread.start()
  1. Passing a Runnable interface implementation as the Thread constructor parameter:
// Creation
class MyRunnable : Runnable {
  override fun run() {
    doSomeWork()
  }
}

  // Usage
val runnable = MyRunnable()
val thread = Thread(runnable)
thread.start()

To see a working example, in your MainActivity.kt under onCreate(), set methodToUse = MethodToDownloadImage.Thread. This makes sure that, when the button is clicked, the method getImageUsingThread() is called. Here is the method definition:

​```kotlin
fun getImageUsingThread() {
// Download image
val thread = Thread(myRunnable)
thread.start()
}```

Where myRunnable has the below implementation:

inner class MyRunnable : Runnable {
  override fun run() {
    // Download Image
    val bmp = DownloaderUtil.downloadImage()

    // Update UI on the UI/Main Thread with downloaded bitmap
    runOnUiThread {
      imageView?.setImageBitmap(bmp)
    }
  }
}

Run the app.

Download image using Thread
Download image using Thread

When you click the Start button, you will see that the image is downloaded and displayed in the ImageView without blocking the UI; the spinner animates while the image is being downloaded.

It’s important to note how the downloaded image has been passed to the UI thread using the runOnUiThread() function that you inherit from the Activity class.

Note: The animation will stop now just for a brief time. Passing an image from one thread to another never comes free.

inner class MyRunnable : Runnable {
  override fun run() {
    // Download Image
    val bmp = DownloaderUtil.downloadImage()

    // Update UI on the UI/Main Thread with downloaded bitmap
    runOnUiThread {
      imageView?.setImageBitmap(bmp)
    }
  }
}

Interacting with UI components from a background thread would have caused an error like this:

E/AndroidRuntime: FATAL EXCEPTION: Thread-4
    Process: com.raywenderlich.android.asyncwars, PID: 3127
    android.view.ViewRootImpl$CalledFromWrongThreadException: 
      Only the original thread that created a view hierarchy can touch its views.

The operating system’s scheduler is responsible for the management of the lifecycle of each thread. It can execute, suspend and resume threads depending on its state and some synchronization requirement. This is an expensive job and, if you try to launch a high number of threads — a million, for example — your processor will spend more time changing from one thread to another than executing the code you want it to execute. This is called context switch. Every Thread you instantiate in Java (or Kotlin) corresponds to a thread of the operating system (either physical or virtual), and, therefore, it is the scheduler of the operating system that is in charge of prioritizing which thread should be executed in every moment.

In a nutshell, threads might be:

  • Expensive: Context switching and having upper limits in the number of threads that can be spawned.
  • Difficult: Creating a multithreaded program is quite complex, requiring a lot of ceremonies around how the code is referenced and executed across the threads.

Taking that into account, engineers working on the Android framework came up with a solution to handle this scenario of doing work on the background thread to then publish it to the UI thread; it is called AsyncTask.

AsyncTask

In Java, you usually put the code you want to run asynchronously into the run method of a class, which implements the Runnable interface. This works well if all you need to do is offload work to another thread. However, it becomes cumbersome when you need to relay the results of that thread back to the UI thread.

When Google adopted Java for Android, it released a new type of class called AsyncTask that made it easier to offload long-running tasks to a background thread, then update the UI thread with the result if there was one. Using AsyncTask instances certainly was easier than Runnable, but it came with its own set of issues.

AsyncTask is the most basic Android component for threading. It’s simple to use and can be good for basic scenarios. The only important thing you should know here is that only one method of this class is running on another thread: doInBackground. The other methods are running on UI thread.

AsyncTask Process Flow
AsyncTask Process Flow

Sample usage

class ExampleActivity : Activity() {

  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    MyTask().execute(url)
  }

  private inner class MyTask : AsyncTask<String, Void, String>() {

    override fun doInBackground(vararg params: String): String {
      val url = params[0]
      return doSomeWork(url)
    }

    override fun onPostExecute(result: String) {
      super.onPostExecute(result)
      // do something with result
    }
  }
}

To see a working example, in your MainActivity.kt file under the onCreate() function, set methodToUse = MethodToDownloadImage.AsyncTask.

This makes sure that, when the button is clicked, the method getImageUsingAsyncTask() is called. Here is the method definition:

fun getImageUsingAsyncTask() {
  // Download image
  val myAsyncTask = GetImageAsyncTask(imageDownloadListener)
  myAsyncTask.execute()
}

Here, GetImageAsyncTask has the below implementation:

class GetImageAsyncTask(val imageDownloadListener: ImageDownloadListener) :
    AsyncTask<String, Void, Bitmap>() {


    // This executes on the background thread
    override fun doInBackground(vararg p0: String?): Bitmap? {
        // Download Image
        return DownloaderUtil.downloadImage()
    }

    // This executes on the UI thread
    override fun onPostExecute(bmp: Bitmap?) {
        super.onPostExecute(bmp)
        if (isCancelled) {
            return
        }

        // Pass it to the listener
        imageDownloadListener.onSuccess(bmp)

        // Cancel this async task after everything is done.
        cancel(false)
    }
}

ImageDownloadListener is used to set up a listener, which will return the bitmap once it is downloaded. In the MainActivity.kt, an instance of this is created and used inside the getImageUsingAsyncTask() method while creating the GetImageAsyncTask, which, in turn, is used to update the UI:

private val imageDownloadListener = object : ImageDownloadListener {
  override fun onSuccess(bitmap: Bitmap?) {
    // Update UI with downloaded bitmap
    imageView?.setImageBitmap(bitmap)
  }
}

Run the app.

Download image using AsyncTask
Download image using AsyncTask

When you click the Start button, you will see that the image is downloaded and displayed in the ImageView without blocking the UI; the spinner animates while the image is being downloaded.

The image downloads:

Download image using AsyncTask
Download image using AsyncTask

The AsyncTask defines some callback methods to simplify the way cancellation and the progression of the task are communicated to the UI; however, it does not play out well when it comes to doing complex operations based on an Android component’s lifecycle. It is actually unaware of the Activity’s lifecycle; in other words, if the activity is destroyed, the AsyncTask doesn’t know about it in the onPostExecute() method unless you tell it.

It is worth noting that even something as simple as screen rotation can cause the activity to be destroyed. Also, canceling an AsyncTask just puts it in a canceled state — it’s up to you to check whether it’s been canceled and halt operations.

Handlers

Handler is part of the HaMeR Framework (Handler, Message & Runnable), which is the recommended framework for communication between threads in Android. This is the one used, under the hood, by the AsyncTask class.

As you have seen in the previous chapters, threads can share data using queues, which usually have a producer and a consumer. The producer is the object that puts data into the queue, and the consumer is the object that reads the data from the queue when available.

If the producer runs on thread A and the consumer on thread B, you can use the queue as a communication channel between different threads. This is the idea behind the HaMeR framework. The queue is actually a MessageQueue, and the data you pass are encapsulated into a Message object. Each Message can contain some data or the reference to a Runnable implementation that defines the code to execute in the thread of the consumer.

If you had to implement the consumer of the queue on your own, you would probably implement it with a cycle that waits for a Message and, when available, reads and uses the information into it or else run the code into the Runnable object if available. That cycle would be in the run implementation of the related Thread class. Android defines this cycle in a class called Looper. It’s important to note that you decide the destination thread putting the message into the related queue. This also implies that there is only one Looper per Thread.

What’s the role of the Handler in all of this? Each Handler instance is associated with a specific Thread through its Looper. You can bind a Looper to a Handler, passing it as the constructor parameter or by simply creating the Handler instance into the Looper’s thread. You can then use a Handler in two different ways:

  1. You can use it in order to put a Message into the queue that its Looper will read into the associated Thread.
  2. You can also use Handler as the object containing the actual consumer logic. In this case, you usually override the handleMessage(Message?) method like this:
  object handler: Handler(){
    override fun handleMessage(msg: Message?) {
      // Consume the message
    }
  }

This is possible because, when a thread reads a message from its queue, it delegates the actual usage of the data to its handlers.

How can you use all this in order to send data from a background thread to the UI? You just need a Handler associated with the main looper that is available by calling Looper.getMainLooper() and then post an action as a Runnable:

val runnable = Runnable {
    // update the ui from here
}

val handler = Handler(Looper.getMainLooper())
handler.post(runnable)

You can summarize the responsibilities of the different objects as:

  • Looper: Runs a loop on its Thread, waiting for Message instances on its MessageQueue.
  • MessageQueue: Holds a list of messages for a given Thread.
  • Handler: Allows the sending and processing of Message and Runnable to the MessageQueue. It can be used to send and process messages between threads.
  • Message: Contains the description and data that can be created and sent using a Handler.
  • Runnable: Represents a task to be executed.

Handler is then the HaMeR workhorse. It’s responsible for sending Message (data message) and post Runnable (task message) objects to the MessageQueue associated with a Thread.

After delivering the tasks to the queue, the handler receives the objects from the looper and processes the messages at the appropriate time. It can be used to send or post some message or runnable objects between threads, as long as such threads share the same process. Otherwise, it will be necessary to use an Inter Process Communication (IPC) mechanism, like the Messenger class or some Android Interface Definition Language (AIDL) implementation.

To see a working example, in your MainActivity.kt file under the onCreate() function, set methodToUse = MethodToDownloadImage.Handler. This makes sure that, when you click the button, the method getImageUsingHandler() is called. Here is the method definition:

fun getImageUsingHandler() {
    // Create a Handler using the main Looper
    val uiHandler = Handler(Looper.getMainLooper())

    // Create a new thread
    Thread {
      // Download image
      val bmp = DownloaderUtil.downloadImage()

      // Using the uiHandler update the UI
      uiHandler.post {
        imageView?.setImageBitmap(bmp)
      }
    }.start()
  }

Run the app.

Download image using Handler
Download image using Handler

When you click the Start button, you will see that the image is downloaded and displayed in the ImageView without blocking the UI; the spinner animates while the image is being downloaded.

HandlerThreads

The UI thread already comes with a Looper and a MessageQueue. For other threads, you need to create the same objects if you want to leverage the HaMeR framework. You can do this by extending the Thread class as follows:

// Preparing a Thread for HaMeR
class MyLooperThread : Thread() {

  lateinit var handler: Handler

  override fun run() {
    // adding and preparing the Looper
    Looper.prepare()

    // the Handler instance will be associated with Thread’s Looper
    handler = object : Handler() {
      override fun handleMessage(msg: Message) {
        // process incoming messages here
        
      }
    }

    // Starting the message queue loop using the Looper
    Looper.loop()
  }
}

However, it’s more straightforward to use a helper class called HandlerThread, which creates a Looper and a MessageQueue for you. Check out the implementation of getImageUsingHandlerThread() method inside MainActivity.kt of the starter app:

var handlerThread: HandlerThread? = null
fun getImageUsingHandlerThread() {
  // Download image
  // Create a HandlerThread
  handlerThread = HandlerThread("MyHandlerThread")

  handlerThread?.let{
    // Start the HandlerThread
    it.start()
    // Get the Looper
    val looper = it.looper
    // Create a Handler using the obtained Looper
    val handler = Handler(looper)
    // Execute the Handler
    handler.post {
      // Download Image
      val bmp = DownloaderUtil.downloadImage()

      // Send local broadcast with the bitmap as payload
      BroadcasterUtil.sendBitmap(applicationContext, bmp)
    }
  }
}

override fun onDestroy() {
  super.onDestroy()

  // Quit and cleanup any instance of dangling HandlerThread
  handlerThread?.quit()
}

Here, you create an instance of the HandlerThread, passing a name that is useful for debugging purposes. The HandlerThread extends the Thread class and you have to start it to use its Looper. You then access the looper property and pass it as the constructor parameter of the Handler. You can then use the handler that you have created for sending Runnable objects to the HandlerThread.

All of the code you encapsulate into the Runnable object will then be executed in the HandlerThread.

Note: You must call quit() on the HandlerThread instance when the work is done, possibly in the onDestroy() method of the activity to release resources it would be holding.

All of the code you encapsulate into the Runnable object will be then executed into the HandlerThread.

HandlerThread
HandlerThread

Note: When the Activity is destroyed, it’s important to terminate the HandlerThread. This also terminates the Looper.

To see a working example, in your MainActivity.kt file under the onCreate() function, set methodToUse = MethodToDownloadImage.HandlerThread. This makes sure that, when you click the button, the method getImageUsingHandlerThread() is called.

Run the app.

When you click the Start button, you will see that the image is downloaded and displayed in the ImageView without blocking the UI.

The spinner animates while the image is being downloaded:

Download image using HandlerThread
Download image using HandlerThread

Service

The definition of a component implies the existence of a container. You usually describe all your components to the container using some document; the container will create, suspend, resume and destroy components depending on the state of the application or on the available resources on the device.

You would say that the container is responsible for the component’s lifecycle. You can apply the same concept to Android when you describe all your components to the system using the AndroidManifest.xml file.

In the example you’ve seen earlier, the component is an Activity whose lifecycle depends mainly on the application usage and the available resources. For instance, when the user rotates the device, the activity is destroyed and then re-created — unless you don’t configure it differently.

What happens when you start a task in the background from an Activity and then rotate the device? In the case of the HandlerThread, you should make it aware of the lifecycle and cancel any tasks, if any, and execute them again. This is not always the best solution — especially in cases of very long tasks like downloading a file.

For situations like these, Android provides a different component whose lifecycle doesn’t depend on what’s happening on the UI but that can only depend on the available resources: the service. It’s an Android component and, as such, you have to declare it in the AndroidManifest.xml file, and it has a lifecycle different from the activity’s lifecycle.

The Service is a component that you can use as the owner of a very long task because the system will change its state only if it needs resources. You can think of it as a safe place to put your long-running code. It’s important to note that a service does not create its thread and does not run in a separate process unless you explicitly say so.

Sample usage

class ExampleService : Service() {

  fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
    doSomeLongProccesingWork()
    return START_NOT_STICKY
  }

  fun onBind(intent: Intent): IBinder? {
    return null
  }

  fun doSomeLongProccesingWork(){
    // Do some work
    // Stop service when required
    stopSelf()
  }
}

It is your responsibility to stop a Service when its work is completed by calling either the stopSelf() or the stopService() method. The Service doesn’t know what is going on in the code running in your thread or executor task — it is your responsibility to let it know when you’ve started and when you’ve finished.

A basic service can exist in two flavors:

  • A started service is initiated by a component in your application and remains active in the background of the device, even if the original component is destroyed. When a started service finishes running its task, the service will stop itself. A standard started service is generally used for long-running background tasks that do not need to communicate with the rest of the app.

  • A bound service provides a client/server communication paradigm. The service is usually thought of as the server and an Android context, usually an activity, is the client. This type of service is similar to a started service, and it also provides callbacks for various app components that can bind to it. When all bound components have unbound themselves from the service, the service will stop itself.

It is important to note that these two ways to run a service aren’t mutually exclusive so you can start a service that will run indefinitely and have components bound to it.

However, since Api Level 26 (Android 8.0), the Service usage as you might know it today,  has been deprecated. It is no longer allowed to fulfill its primary purpose, namely to execute a long-running task in the background. Calling startService() method when your app has been put in background throws an IllegalStateException. The only way one can use services now is as a foreground service.

Intent service

As stated previously, Service components, by default, are started in the main thread like any other Android component. If you need the service to run a task as a background task, then it’s up to you to create a separate thread and move your work to that thread. The Android frameworks also offers a sub-class of Service that can do all the threading work for you: IntentService.

It runs on a separate thread and stops itself automatically after it completes its work. IntentService is usually used for short tasks that don’t need to be attached to any UI. Since IntentService doesn’t attach to any activity and it runs on a non-UI thread, it serves that need perfectly. Moreover, IntentService stops itself automatically, so there is no need to manually manage it, either.

One of the biggest issues with a standard started service is that it cannot handle multiple requests at a time, but that is not the case with an IntentService. It creates a default worker thread for executing all intents that are received in onStartCommand(), so all operations can happen off the main thread. It then creates a work queue for sending each intent to onHandleIntent() one at a time so that you don’t need to worry about multi-threading issues.

Essentially, there is always only one instance of your IntentService implementation at any given time and it has only one HandlerThread. This means that if you need more than one thing to happen at the same time, IntentServices may not be a good option.

Sample usage

// Required constructor with a name for the service
class MyIntentService : IntentService("MyIntentService") {

  override fun onHandleIntent(intent: Intent?) {
    //Perform your tasks here
    doSomeWork();
  }
}

To see a working example, in your MainActivity.kt file under the onCreate() function, set methodToUse = MethodToDownloadImage.IntentService. This makes sure that when the button is clicked, the method getImageUsingIntentService() is called. Here is the method definition:

fun getImageUsingIntentService() {
  // Download image
  val intent = Intent(this@MainActivity, MyIntentService::class.java)
  startService(intent)
}

Here, MyIntentService has the below implementation:

// Required constructor with a name for the service
class MyIntentService : IntentService("MyIntentService") {

  override fun onHandleIntent(intent: Intent?) {
    // Download Image
    val bmp = DownloaderUtil.downloadImage()

    // Send local broadcast with the bitmap as payload
    BroadcasterUtil.sendBitmap(applicationContext, bmp)
  }
}

Here, BroadcasterUtil is a utility class that internally uses LocalBroadcastManager. It is used here to easily send the image back to the UI thread. You will learn more about this process in the next section. Run the app.

When you click the Start button, you will see that the image is downloaded and displayed in the ImageView without blocking the UI; the spinner animates while the image is being downloaded.

Download image using IntentService
Download image using IntentService

Sending data from a Service to the UI

You learned that a started Service is an Android component that is not bound to the UI. If you need to send some data from a service to a different component, like an Activity, you need some other mechanisms like the LocalBroadcastManager that you used via the BroadcasterUtil in the previous example. You can see how to send data from a service in the onHandleIntent() method of the MyIntentService class:

override fun onHandleIntent(intent: Intent?) {
  // Download Image
  val bmp = DownloaderUtil.downloadImage()

  // Send local broadcast with the bitmap as payload
  BroadcasterUtil.sendBitmap(applicationContext, bmp)
}

Here, sendBitmap(applicationContext, bmp) is a method defined inside BroadcasterUtil class as shown below:

/**
  * Send local broadcast with the bitmap as payload
  * @param context Context
  * @param bmp Bitmap
  * @return Unit
  */
fun sendBitmap(context: Context, bmp: Bitmap?) {
    val newIntent = Intent()
    bmp?.let {
        newIntent.putExtra("bitmap", it)
        newIntent.action = MainActivity.FILTER_ACTION_KEY
        LocalBroadcastManager.getInstance(context).sendBroadcast(newIntent)
    }
}

As you can see, it uses LocalBroadcastManager to send a broadcast using an intent, which has a payload of the passed bitmap. A LocalBroadcastManager needs a BroadcastReceiver to be registered using the registerReceiver() method. In the starter app, there is an implementation for a BroadcastReceiver already provided named MyBroadcastReceiver, which is shown below:

class MyBroadcastReceiver(val imageDownloadListener: ImageDownloadListener) : BroadcastReceiver() {
  override fun onReceive(context: Context, intent: Intent) {
      val bmp = intent.getParcelableExtra<Bitmap>("bitmap")

      // Pass it to the listener
      imageDownloadListener.onSuccess(bmp)
  }
}

ImageDownloadListener is used here to set up a listener, which will return the bitmap once it is downloaded. In the MainActivity.kt, you’ve already created an instance of this during the AsyncTask section of this chapter.

BroadcasterUtil abstracts the register and unregister methods of MyBroadcastReceiver for the LocalBroadcastManager by defining helper methods:

/**
  * Register Local Broadcast Manager with the receiver
  * @param context Context
  * @param myBroadcastReceiver MyBroadcastReceiver
  * @return Unit
  */
fun registerReceiver(context: Context, myBroadcastReceiver: MyBroadcastReceiver?) {
    myBroadcastReceiver?.let {
        val intentFilter = IntentFilter()
        intentFilter.addAction(MainActivity.FILTER_ACTION_KEY)
        LocalBroadcastManager.getInstance(context).registerReceiver(it, intentFilter)
    }
}

/**
  * Unregister Local Broadcast Manager from the receiver
  * @param context Context
  * @param myBroadcastReceiver MyBroadcastReceiver
  * @return Unit
  */
fun unregisterReceiver(context: Context, myBroadcastReceiver: MyBroadcastReceiver?) {
    myBroadcastReceiver?.let {
        LocalBroadcastManager.getInstance(context).unregisterReceiver(it)
    }
} 

You use these helper methods later to register and unregister an instance of MyBroadcastReceiver to the LocalBroadcastManager in onStart() and onStop() respectively, of the MainActivity:

// ----------- Lifecycle Methods -----------//
override fun onStart() {
  super.onStart()
  BroadcasterUtil.registerReceiver(this, myReceiver)
}

override fun onStop() {
  super.onStop()
  BroadcasterUtil.unregisterReceiver(this, myReceiver)
}

Important points to note, here:

  • If there’s no BroadcastReceiver registered, there won’t be any update in the UI.
  • The thread that will perform the ImageView update is the UI thread.
  • IntentService uses HandlerThread internally.

Executors

You’ve seen that you can encapsulate code into a Runnable implementation in order to eventually run it in some given Thread. Every object that can execute what’s defined as a Runnable can be abstracted using the Executor interface, introduced in Java 5.0 as part of the concurrent APIs.

interface Executor {
    fun execute(command: Runnable)
}

You can execute a Runnable in many different ways. You can, for instance, simply invoke directly the run() method or pass the Runnable object as a constructor parameter of the Thread class and start it, as seen previously. In the former case, you’re executing the runnable code in the caller thread. In the latter, you’re executing the same code into a different thread. This depends on the particular Executor implementation.

Creating a thread is simple in code but expensive in practice. Every time you create a Thread instance you need to request resources from the operative system and every time the thread completes its job — when its run() method ends — it must be garbage collected. The typical solution is the usage of a thread pools, which need some kind of lifecycle.

The pool needs to be initialized with a minimum number of threads. When the application ends, the pools should shut down and release all their resources. Even when the pool is active, you can have a different policy for the minimum number of instances of threads to keep alive or how to manage the creation of new instances when needed. You could limit the number of threads, forcing the client to wait, or create a new thread every time you need to run something. There is more than the simple Executor interface and that is the ExecutorService interface.

The ExecutorService is then the abstraction for a specific Executor, which needs to be initialized and shut down to allow for the execution of Runnable objects in an efficient and optimized way. The way this happens depends on the specific implementation. One of the most important classes is the ThreadPoolExecutor. It manages a pool of worker threads and a queue of tasks to execute.

Depending on the configured policy, it reuses an available thread or creates a new one to consume the tasks from a queue.

The concurrent APIs provide different implementations that are available through some static factory methods of the Executors class. The most common is Executors.newSingleThreadExecutor(), which create an executor that will process a single task at a time, and Executors.newFixedThreadPool(N), which creates an executor with an internal pool of N threads.

It’s important to note that an ExecutorService also provides the option of executing Callable<T> implementations. While the Runnable interface defines a run() method, which returns Unit, a Callable<T> is a generic interface, which defines the call() method that returns an object of type T:

interface Callable<T> {
    fun call(): T
}

You can think of a Callable<T> as a Runnable that returns an object of type T at the end of the task. You can ask the ExecutorService to run the given Callable<T> using the invoke() method, getting a Future<T> in return. The Future<T> provides a get() method, which blocks until the result of type T is available or throws an exception in case of error or interruption.

Sample usage

val executor = Executors.newFixedThreadPool(4)
(1..10).forEach {
  executor.submit {
    print("[Iteration $it] Hello from Kotlin Coroutines! ")
    println("Thread: ${Thread.currentThread()}")
  }
}

To see a working example, in your MainActivity.kt file under the onCreate() function, set methodToUse = MethodToDownloadImage.Executor. This makes sure that, when you click the button, the method getImageUsingExecutors() is called. Here is the method definition:

fun getImageUsingExecutors() {
  // Download image
  val executor = Executors.newFixedThreadPool(4)
  executor.submit(myRunnable)
}

Here, myRunnable in the MainActivity.kt is an instance of MyRunnable, which you’ve already created during the Thread section of this chapter.

Run the app.

When you click the Start button, you will see that the image is downloaded and displayed in the ImageView without blocking the UI; the spinner animates while the image is being downloaded.

Download image using Executor
Download image using Executor

The main advantages of using ThreadPoolExecutor in an Android application are:

  • Powerful task execution framework as it supports task addition in a queue, task cancellation, and task prioritization.
  • Reduces the overhead associated with thread creation as it manages a required number of threads in its thread pool.
  • Reduces boilerplate code as it abstracts most of the codebase behind factory methods with sane defaults.

However, although ExecutorService implementations provide an optimized usage of threads in terms of creation and reuse, they don’t solve the problems related to context switching between threads.

WorkManager

Announced at Google I/O 2018 as part of Jetpack, WorkManager aims to simplify the developer experience by providing a first-class API for system-driven background processing. The WorkManager API makes it easy to specify deferrable, asynchronous tasks and when they should run. It is intended for background jobs that should run even if the app is no longer in the foreground. Where possible, it delegates its work to a JobScheduler, Firebase JobDispatcher, or Alarm Manager + Broadcast receivers depending on the Android version. If your app is in the foreground, it will even try to do the work directly in your process. The task is still guaranteed to run, even if your app is force-quit or the device is rebooted.

WorkManager chooses the appropriate way to run your task based on such factors as the device API level and the app state.

By default, WorkManager runs each task immediately, but you can also specify the conditions the device needs to fulfill before the task can proceed, including network conditions, charging status and the amount of storage space available on the device. If WorkManager executes one of your tasks while the app is running, it can run your task in a new thread in your app’s process.

If your app is not running, WorkManager chooses an appropriate way to schedule a background task — depending on the device API level and included dependencies. You don’t need to write device logic to figure out what capabilities the device has and choose an appropriate API; instead, you can just hand your task off to WorkManager and let it choose the best option.

WorkManager Process Flow
WorkManager Process Flow

Sample usage

// A simple Worker
class DoSomeWorker : Worker() {
    // This method will run in background thread and WorkManger 
    // will take care of it
    override fun doWork() : WorkerRequest() { 
        doSomeWork()
        return WorkResult.SUCCESS
    } 
}

// Usage
// Create the request
val request : WorkRequest = OneTimeWorkRequestBuilder<DoSomeWorker>()
                             .build()
// Enqueue the request
val workManager : WorkManager = WorkManager.getInstance()
workManager.enqueue(request)

In short, the WorkManager is another library that is trying to solve the old problem of executing long-running jobs on the Android platform. It delegates the logic to different components that are available only on specific versions of the platform. If you decide to use this library, you accept all the fallbacks and workarounds used to enable support for older platforms/APIs. WorkManager is seen as the third attempt by Google to solve the job management problem on the Android Platform and will probably not be the last.

RxJava + RxAndroid

Reactive programming is an asynchronous programming paradigm concerned with data streams and the propagation of change. The essence of reactive programming is the observer pattern.

Note: The observer pattern is a software design pattern wherein data sources or streams, called observables, emit data and one or more observers, who are interested in getting the data, subscribe to the observable.

In reactive programming, you are allowed to create data streams from anything including Array, ArrayList, etc. These data streams can be observed, modified, filtered or operated upon. You can use a stream as an input to another one. You can even use multiple streams as inputs to another stream.

You can merge two streams. You can filter a stream to get another one that has only those events you are interested in. You can map data values from one stream to another.

A typical data stream can emit three different values: one when the event occurs, one when an error occurs or one when the event is completed.

RxJava is a library that makes it easier for you to implement reactive programming principles on any JVM-based platform, including Android. To manage threads, RxJava has a helper class called Schedulers. Schedulers are how you tell where the observer and observables should run.

Some general use Schedulers to observe:

  • Schedulers.computation(): Used for CPU intensive tasks.
  • Schedulers.io(): Used for IO bound tasks.
  • Schedulers.from(Executor): Used with custom ExecutorService.
  • Schedulers.newThread(): It always creates a new thread when a worker is needed.

This is where RxAndroid library comes into the picture, which plays a major role in supporting multi-threading concepts in Android applications. It provides a Scheduler that schedules on the main thread or any given Looper.

Sample usage

Observable.just("Hello", "from", "RxJava")
        .subscribeOn(Schedulers.newThread())
        .observeOn(AndroidSchedulers.mainThread())
        .subscribe(/* an Observer */);

This will execute the Observable on a new thread and emit results through onNext() on the main thread.

To see a working example, in your MainActivity.kt file under the onCreate() function, set methodToUse = MethodToDownloadImage.RxJava. This makes sure that when you click the button, the method getImageUsingRx() is called. Here is the method definition:

var single: Disposable? = null
fun getImageUsingRx() {
  // Download image
  single = Single.create<Bitmap> { emitter ->
    DownloaderUtil.downloadImage()?.let { bmp ->
      emitter.onSuccess(bmp)
    }
  }.observeOn(AndroidSchedulers.mainThread())
    .subscribeOn(Schedulers.io())
    .subscribe { bmp ->
      // Update UI with downloaded bitmap
      imageView?.setImageBitmap(bmp)
    }
}

override fun onDestroy() {
  super.onDestroy()

  // Cleanup disposable if it was created i.e. not null
  single?.dispose()
}

Note: It is important that you call dispose() on the Single instance when the work is done, possibly in the onDestroy() of the activity to release resources it would be holding and close the stream.

Also note that the topic of reactive extensions is pretty vast; covering the mechanics of its functionalities is out of the scope of this book.

Run the app.

Download image using RxJava
Download image using RxJava

When you click the Start button, you will see that the image is downloaded and displayed in the ImageView without blocking the UI; the spinner animates while the image is being downloaded.

Download image using RxJava
Download image using RxJava

Although reactive programming is a compelling tool and solves a lot of complex concurrency problems, the learning curve for RxJava is very steep and complex. It is a different approach towards programming and can lead to some confusion when programming larger apps.

Coroutines

Now that you have a clear idea about various ways of doing asynchronous work in Android, as well as the pros and cons, let’s come back to Kotlin coroutines. Kotlin coroutines are a way of doing things asynchronously in a sequential manner. Creating coroutines is cheap versus creating threads.

Note: Coroutines are completely implemented through a compilation technique (no support from the VM or OS side is required), and suspension works through code transformation.

Coroutines are based on the idea of suspending functions: functions that can stop the execution when they are called and make it continue once it has finished running their own task. Enabling Kotlin coroutines in Android involves just a few simple steps. To show how easy it is to enable coroutines, head back to the starter project and add the Android coroutine library dependency into your app’s build.gradle file under dependencies block, replacing the line // TODO: Add Kotlin Coroutine Dependencies here with the following:


dependencies {
  ..
  // Coroutines
  final def coroutineVer = "1.3.0"
  implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutineVer"
  implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutineVer"
}

Note: To use Coroutines v1.3.0, make sure that the Kotlin standard library is at least v1.3.50

Next, inside your MainActivity.kt file, add the implementation for the method getImageUsingCoroutines() by replacing // TODO: add implementation here with the below code snippet:

GlobalScope.launch {
  // Download Image in background
  val deferredJob = async(Dispatchers.IO) {
    DownloaderUtil.downloadImage()
  }
  withContext(Dispatchers.Main) {
    val bmp = deferredJob.await()
    // Update UI with downloaded bitmap
    imageView?.setImageBitmap(bmp)
  }
}

To see a working example, in your MainActivity.kt file under the onCreate() function, set methodToUse = MethodToDownloadImage.Coroutine.

This makes sure that when you click the button, the method getImageUsingCoroutines() is called.

Run the app.

Download image using Coroutine
Download image using Coroutine

When you click the Start button, you will see that the image is downloaded and displayed in the ImageView without blocking the UI; the spinner animates while the image is being downloaded.

A lot has already been explained about the mechanics of Kotlin coroutines in the previous chapters; in the subsequent chapters, you will mostly cover the usage of Kotlin coroutines in Android apps.

Introducing Anko

While Kotlin does remove much of the verbosity and complexity typically associated with Java, no programming language is perfect and, thus, libraries that build on top of the language are born. Anko is one such library that uses Kotlin and provides a lot of extension functions to make your Android development easier.

Note: That’s how Anko got its name: (An)droid (Ko)tlin.

Anko was originally designed as a single library. As the project grew, adding Anko as a dependency began to have a significant impact on the size of the APK (Android Application Package).

Today, Anko is split across several modules:

  • Commons: Helps you perform the most common Android tasks, including displaying dialogs and launching new Activities.
  • Layouts: Provides a Domain Specific Language (DSL) for defining Android layouts.
  • SQLite: A query DSL and parser that makes it easier to interact with SQLite databases.
  • Coroutines: Supplies utilities based on the kotlinx.coroutines library.

You can see the differences in a sample comparison, below.

Using language provided coroutines:

button.setOnClickListener {
  launch(UI){
    val userId = fetchUserString("user_id_1").await()
    val user = deserializeUser(userId).await()
    showUserData(user)
  }
}

Using an Anko-provided coroutine helper:

button.onClick {
  val userId= bg { fetchUserString("user_id_1").await() }
  val user = bg { deserializeUser(userId).await() }
  showUserData(user)
}

onClick and bg are some of the many helper functions Anko provides for making the process of handling coroutines even simpler, which will be covered in depth in later chapters.

Key points

  • Android is inherently asynchronous and event-driven, with strict requirements as to which thread certain things can happen on.

  • The UI thread — a.k.a., main thread — is responsible for interacting with the UI components and is the most important thread of an Android application.

  • Almost all code in an Android application will be executed on the UI thread by default; blocking it would result in a non-responsive application state.

  • Thread is an independent path of execution within a program allowing for asynchronous code execution, but it is highly complex to maintain and has limits on usage.

  • AsyncTask is a helper class that simplifies asynchronous programming between UI thread and background threads on Android. It does not work well with complex operations based on Android Lifecycle.

  • Handler is another helper class provided by Android SDK to simplify asynchronous programming but requires a lot of moving parts to set up and get running.

  • HandlerThread is a thread that is ready to receive a Handler because it has a Looper and a MessageQueue built into it.

  • Service is a component that is useful for performing long (or potentially long) operations without any UI, and it runs in the main thread of its hosting process.

  • IntentService is a service that runs on a separate thread and stops itself automatically after it completes its work; however, it cannot handle multiple requests at a time.

  • Executors is a manager class that allows running many different tasks concurrently while sharing limited CPU time, used mainly to manage thread(s) efficiently.

  • WorkManager is a fairly new API developed as part of JetPack libraries provided by Google, which makes it easy to specify deferrable, asynchronous tasks and when they should run.

  • RxJava + RxAndroid are libraries that make it easier to implement reactive programming principles in the Android platform.

  • Coroutines make asynchronous code look synchronous and work pretty well with the Android platform out of the box.

  • Anko is a library that uses Kotlin and provides a lot of extension functions to make our Android development easier.

Where to go from here?

Phew! That was a lot of background on asynchronous programming in Android! But the good thing is that you made it!

In the upcoming chapters, you will dive deeper into how you can leverage coroutines in Android apps to handle async operations while keeping in sync with various nuances of the Android platform, such as respecting lifecycles of an app and efficient context switching to facilitate the various use cases of apps to fetch-process-display data.

Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.
© 2026 Kodeco Inc.