Asynchronous Programming in Android (Part 1)

Faiz Anwar
7 min readFeb 29, 2020

--

You can’t get this wrong.

Photo by cheesecakelabs.com

Introduction

I have been researching about asynchronous programming in Android lately .This journey has made me realize the importance of multi threading in android and why every developer should know what happens under the hood.Lets begin with why do we even need asynchronous programming in Android in the very first place?

As we all know UI is the most fundamental part of our application.An elegant user experience is what every developer strives for.However handling seamless UI transitions along with heavy I/O or N/W operations can become difficult to handle.Lets explore a real life scenario to understand this situation better.

Consider a situation where an image is required to be uploaded to the server through our app. User will select an image and click the upload button. The app would be expected to show a loader till upload is completed . Once the operation is completed app is again expected to show a success or failure message based on the result.Lets try to analyze some code responsible for this operation.

Understanding blocking call

In uploadImage(), we assumed that the spinner animation was running even after the completion of showLoadingSpinner(), so that uploading of the image could start. Looking at the function definition, it doesn’t seem possible. If the spinner is animating, it means that showLoadingSpinner() has not completed. If showLoadingSpinner() has completed, then only the upload will start. This means that the spinner is not animating anymore at the time of uploading the image. This is happening because when you invoke showLoadingSpinner() you’re making a blocking call.

A blocking call is essentially a function that only returns when it has completed. In the example above, showLoadingSpinner() prevents the upload of an image because it keeps the main thread of execution busy until it returns. But when it returns (because running becomes false), the spinner stops rotating when the actual upload is taking place.This situation gives an impression of app freeze to the user since the main thread now is performing the upload operation and cannot respond to any user interactions which leads to App Not Responding(ANR).

The main thread we are speaking about is also known as UI thread, because it’s responsible for rendering everything on the screen, and this should be the only thing it does. This means that it should manage the rotation of the spinner but not the upload of the image — that has nothing to do with the UI.Here is when the worker threads come into picture.As a golden rule of thumb we dedicate all the task that are non UI related to the worker threads.Before we proceed further lets see what Google developer’s documentation says about UI thread and non UI operations:

While a screen update is occurring, the system tries to execute a block of work (which is responsible for drawing the screen) every 16ms or so, in order to render smoothly at 60 frames per second. For the system to reach this goal, the UI/View hierarchy must update on the main thread. However, when the main thread’s messaging queue contains tasks that are either too numerous or too long for the main thread to complete the update fast enough, the app should move this work to a worker thread. If the main thread cannot finish executing blocks of work within 16ms, the user may observe hitching, lagging, or a lack of UI responsiveness to input. If the main thread blocks for approximately five seconds, the system displays the Application Not Responding (ANR) dialog, allowing the user to close the app directly.

This is one of the main reason why in the past decade the hardware industry has steered in the direction of increasing the number of cores each processor has and the number of threads each core can run concurrently.Hence increasing the capability of devices to perform more and more tasks at the same time.

Multithreading in android

Let’s try to analyze our code again but from a multi threaded perspective. showLoadingSpinner() starts a new thread that is responsible for rotation of the spinner image, which interacts with the main thread just to notify a refresh in the UI. Starting a new thread, the function is now a non-blocking call and can return immediately, allowing the image upload to start its own worker thread. When completed, this background thread will notify the main thread to hide the spinner.

It’s now important to understand how these threads process the result .The thread responsible for rotating the spinner image needs to communicate with the main thread in order to refresh the UI at each frame.In order to communicate, different threads need to share data. For instance, the thread responsible for the rotation of the spinner image needs to notify the main thread that a new image is ready to be displayed.What happens, for instance, if the main thread receives a notification that a new image is available and, before displaying it, the image is replaced? Sharing data is not simple, and it needs some sort of synchronization, which is one of the main benefits of well written concurrency code.

Accessing the same data from multiple threads, maintaining the correct behavior and good performance, is the real challenge of concurrent programming.The most important data structure that you can use in order to safely share data in a thread are queues .Threads usually communicate using queues, and they can act on them as producers or consumers. A producer is a thread that puts information into the queue, and the consumer is the one that reads and uses them. You can think of a queue as a list in which producers append data to the end, and then consumers read data from the top, following a logic called FIFO (First In First Out). Threads usually put data into the queue as objects called messages, which encapsulate the information to share.

Now after processing is done the result is supposed to be handed back to the main thread. Out of all the asynchronous programming mechanisms callbacks are most widely used .So what exactly is a callback?

It means creation of objects that encapsulate code that somebody else can execute later, like when a specific task completes .We create interfaces to put some code in an object to pass around.Sounds confusing? Let’s check out some code.

Using callbacks

We can implement this interface in our activity/fragment and provide a definition to this method on what it should do once upload is completed.There is a question which comes to mind at this point which is who invokes this method aka how will this callback know when the upload is done? You guessed it right ,the service class responsible for performing the network operation invokes this method after its completion .Lets try to achieve the same result in much more cooler way .

Looks like a simple code ain’t it?You have a sweet loading spinner and then you call the upload service and pass the image to it.How will hideSpinner() be called only after the service has been performed?In modern programming languages like Kotlin, which support functional programming features, you can do the same with a lambda expression.You could pass the lambda to the upload function as a callback. The lambda would then contain the code to execute when the upload task completes, pretty cool right.Callbacks are simpler than building our own mechanisms for thread communication. Their syntax is also fairly readable, when it comes to simple functions.

Let’s talk about the uncool aspect of these callbacks.In enterprise level application it’s often the case that we have multiple function calls, which need to be connected or combined somehow, hence mapping the results into more complex objects.In these cases, the code becomes extremely difficult to write, maintain and reason about. Since you can’t return a value from a callback, but have to pass it down the lambda block itself, you have to nest callbacks. It’s similar to nesting forEach or map statements on collections, where each operation has its own lambda parameter.When nesting callbacks, or lambdas, we get a large number of braces ’{}’, each forming a local scope. This, in turn, creates a structure called indentation hell — or callback hell.

Let’s take a use case where we need to load an image and resize it before uploading it.Here is how the upload method would look like:

The first thing you notice is the amount of braces and indentation that form a stair- like code structure. This makes the code very hard to read, and it’s not even a complex operation. When building services on the web, nesting can easily reach 10 levels, if not more. Not only is the code hard to read, but it’s also extremely hard to maintain such code. Because of the structure, you suffer from cognitive load, making it harder to reason about the functionality and flow. Trying to add a step in between, or change the lambda-result types, will break all the subsequent levels.

What’s Next?

In the next post I will discuss some alternatives to these callbacks and the value they bring.I will also try to shed some light over where asynchronous programming in android is heading towards.Till then thank you for your time and don’t forget to hit the clap button.

--

--