Using Threads in Android and Communicating them with the UI Thread

According to the documentation “By default, all components of the same application run in the same process and thread (called the “main” thread).” This means that if we need to run a long operation, we should execute it in a separate thread, otherwise it may block the UI. Besides that, we also have to coordinate worker threads to the activities/fragments lifecycle.

Android SDK offers some mechanisms that help developers to deal with threads such as AsyncTask, Service (which in fact still needs to manually manage background threads if needed), IntentService, etc. We can also use a regular Java Runnable class or the ThreadPoolExecutor (when need to manipulate multiple threads in parallel). Also, when executing an operation in a worker thread, chances are we need to update our GUI with some sort of data, but Android only allows UI changes from the main thread. Fortunately Android provides some ways to facilitate communication between a worker thread and the main thread.

One thing that we must keep in mind when dealing with threads is configuration changes. If, for example,  a screen rotate occurs while an operation is running in a worker thread, depends on how we implement the communication between a worker thread and main thread, we may end up with old references which can lead to undesired situations.

This article goes along with four demo apps, each one demonstrating an image download process using one of the mechanisms showed below. It will also show some approaches to communicate between a worker thread and the main thread such as LocalBroadcastManager, Handler and ResultReceiver (each of the four demo apps uses one or more communication mechanisms). Then it will show how to deal with orientation changes while a long-running operation is being executed:

Service

A Service is a component designed to execute operations while not interacting with the user. It uses the application’s main thread, by default, so it should be used for short tasks. When a long-running operation needs to be executed, we should start a worker thread inside a service. It can be started from any thread and can communicate directly to the main thread (unless we create a worker thread to do the job).

Once a service is started, it will keep running even if the component that started it is destroyed. So it needs to explicitly be stopped by calling stopSelf() or stopService().

When comparing to its child IntentService, it is more flexible, but this comes at a price, since we need to deal with threads manually.

IntentService

IntentService extends Service class and runs on its own thread, so we do not need to deal with thread stuff. It is designed to execute long-running operations, and every request (i.e.: a call to startService method) is automatically queued which means only a single request is processed at a time.

Unlike its parent class Service, it stops itself when there is no intent in the queue.

Although it hides threads complexity, it is less flexible than a Service. One of its limitation is that there is no way to run parallel threads.

AsyncTask

AsyncTask is a convenient mechanism provided by the android SDK which allows us to easily work with threads without deal with its complexity. It also provides methods to properly communicate with the main thread. It is not designed for long operation.

To use it we need to create an instance of AsyncTask and implement at least doInBackground(Params…) method. This is where we put our code which will run in a worker thread. If we want to publish a result at the end of its execution, we need to override onPostExecute(Result) which runs in the main thread. During its execution, if we need to communicate to the main thread, we just call publishProgress(int) and override its onProgressUpdate(Integer… progress) which also runs in the main thread.

As stated in the documentation, the three types used by an asynchronous task are the following:

  1. Params, the type of the parameters sent to the task upon execution.
  2. Progress, the type of the progress units published during the background computation.
  3. Result, the type of the result of the background computation.

We do not need to use all of these parameters. To mark a type as unused, simply use the type Void. In the snippet below, we will pass a URL for an image to be downloaded (first parameter of type String), we will inform the percentage of the downloaded (second parameter of type String), and a Bitmap will be used as the result (third parameter of type Bitmap).

To start an AsyncTask, we call its execute method. Once we call it, onPreExecute is invoked (on the UI thread) and thereafter doInBackground (on the background thread). The task can be executed only once and an exception will be thrown if a second execution is attempted.

Manual Thread Management

If none of the mechanisms provided by Android SDK works for you, you can always manage threads by yourself using mechanisms such as Runnable or ThreadPoolExecutor. This last one is more suitable when we need to run multiple threads in parallel.

When managing threads manually in an Android app, there are some points we need to pay a close attention to not end up with issues such as leaks, old references, etc. Although manual threads management is out of the scope of this article, we provide a demo app which uses such mechanism. We suggest you to take a look on this article. It contains useful information to prevent issues when using threads manually. 

Communication between worker threads and main UI thread

Due the Android design, a worker thread cannot directly communicate to the main thread. This means we cannot just hold a reference to an Activity and use it to change a GUI element. If we try to do it, we will get a RuntimeException with the message “Can’t create handler inside thread that has not called Looper.prepare()”. Instead, we need to ask Android to run a piece of code in the main thread. One way to do it is to call Activity#runOnUiThread method. Also we can use other approaches like LocalBroadcastManager, Handler, ResultReceiver, etc.

LocalBroadcastManager

LocalBroadcastManager is a component used to send/receive broadcasts of intents anywhere in the app. To use this mechanism, we need to create an instance of the BroadcastReceiver class and implement its onReceive method. Then, register any actions we are interesting in to the LocalBroadcastManager.

Rembemer BroadcastReceiver#onReceive method is always called within the main thread of its process, unless you explicitly ask for it to be scheduled on a different thread using registerReceiver(BroadcastReceiver, IntentFilter, String, android.os.Handler). 

We can unregister a local broadcast when we are no longer interesting in receive messages. Usually we register a local broadcast in the onCreate or onResume and unregister it in the onDestroy or onPause methods, but this can be different depends on your requirements.

Now, when need to send a message, use the LocalBroadcastManager to broadcast it:

Handler

Docs states that A Handler allows you to send and process Message and Runnable objects associated with a thread’s MessageQueue.”. It also states that “When you create a new Handler, it is bound to the thread / message queue of the thread that is creating it…”. So, this means that if we create a Handler in the main thread, it will be associated with it. Later, when we send a message to that handler, no matter which thread we use to send messages, they will be processed inside the main thread.

Let’s see some code. First we create our Handler in the main thread:

Then, when it comes to start our thread, we just pass the handler instance to the thread:

Inside the class which will execute the background operation, when we need to send some data back to the main thread, we simply use that handler instance, create a message, wrap on it everything we want (by using a Bundle) and send it. This message will be processed by the Handler#handleMessage. Since we created our Handler in the main thread, it will be able to change any UI object.

ResultReceiver

ResultReceiver is a generic interface for receiving a callback result from someone. To use it, we need to subclass it and implement onReceiveResult(int, Bundle) method.

Then, we can pass an instance of it to the component which we need to interact with. This is demonstrated in the ImageDownloader_IntentService example. We pass a DownloadResultReceiver instance (i.e. a subclass of ResultReceiver) when calling startService method:

Later, inside the service, we get that ResultReceiver instance and call ResultReceiver#send method with the data we want to send back (wrapped in a Bundle).

Configuration Change

Maybe the hardest part when using threads is to coordinate it with the Activity/Fragment lifecycle. If we are executing an operation in a worker thread, what should happen with it if the device is rotated? We may not want to kill the current thread and start it over again (although this might be an option depends on the requirements). Actually activities are destroyed and so Fragments (by default). So, if after a screen rotation a running thread needs to communicate to the main thread by using a reference to the activity or the fragment which created it (or one of its objects references), this might be a problem.

If we are using LocalBroadcastManager mechanism, there is nothing to worry about. After a configuration change we just need to register the actions again. But when using either a Handler or a ResultReceiver, since we need to pass a reference of it to the thread, after a configuration change happens, that reference will be out of date. This is by the fact that Activities and Fragment are destroyed and recreated by default. Also, by retaining an old Activity reference in a thread, that Activity will never be eligible to to the garbage collector.

In order to fix it we can use a retained fragment. Due the nature of a retained fragment, it will not be destroyed during a configuration change. Later, after the configuration change, inside the new Activity instance, we can get back a reference for that fragment (from the FragmentManager) and set the target fragment to point to the new Activity instance. Doing so, when the worker thread needs to communicate to the activity, it will always have the right instance reference.

Let’s see a step by step used in the demo app. We can find it on both ImageDownloader_AsyncTask and ImageDownloader_Thread demo apps:

  • (ImageDownloaderFragment#onCreate) Create a fragment and mark it as retained by calling Fragment:setRetained(true).
  • (MainFragment#onCreate) Create an ImageDownloaderFragment instance  and add it to the FragmentManager.
  • (MainFragment#onCreate) Set ImageDownloaderFragment target fragment so that it will be able to send the result back to the MainFragment.
  • (ImageDownloaderFragment#startDownload) Inside the retained fragment, when it comes to start a download, start the thread in which the long-running operation will be executed. Pass a “this” reference to it, since this fragment will not be destroyed during an orientation change.
  • (MainFragment#onSaveInstanceState) When a configuration change is detected, save the current file name for the image being downloaded (if any).
  • (MainFragment#onSaveInstanceState) If the MainFragment already contains an image, this means a download already finished, so, store that image.
  • (MainFragment#onCreate) After a configuration change, retrieve the ImageDownloaderFragment instance from the FragmentManager. This will give us the same instance created before the orientation change.
  • (MainFragment#onCreate) If savedInstanceState is not null, get the current file name for the image. This may be useful if the image is still being downloaded.
  • (MainFragment#onCreate) Call ImageDownloaderFragment #setTargetFragment to point the new MainFragment instance, so that it will be able to send the result back to the MainFragment.
  • (MainFragment#onCreateView) In case of an orientation change (i.e.: savedInstanceState not null), check whether bundle contains a bitmap. If so, it means a download has finished prior the orientation change, so we can get it from the bundle and display it. Note we cannot do this inside onCreate method, since views are not being inflated yet.

Now, let’s see a snippet which shows some of the steps above:

A good explanation about retained fragments can be found here.

Conclusion

Dealing with threads in Android apps might not be something trivial, but definitely this is something we soon or later will need to deal with, so, we should really spend some time trying to understand how it really works. Android SDK offers some mechanisms to execute long-running operations in worker thread as well as to facilitate threads communication. We covered only some topics about it. There are much more to learn, especially if we want to run multiples threads at a time, but this will be discussed in a future article.

As usual, if you have any question, leave a message below.