Threading With HandlerThread in Android

0
24

Responsiveness is one of the most valuable features of any app. This is even more important in resource-constrained devices like smartphones. Users must be able to interact with the app in a smooth way without falling into flickering or a slow UI. For these reasons, concurrent programming must now be a part of the skillset of every developer.

It’s very important to know that all the interactions with UI components in an Android app happen on a single thread that we call the main thread — or, simply, the UI thread. Experience in other environments (Swing, AWT and others) teaches that the best approach is the one that translates every operation on the UI into a message to a single consumer that runs on the main thread. In Android, you can do this in many ways using different tools. HandlerThread is one of these.

In this tutorial, you’ll develop a handy food-ordering app that needs concurrent programming to execute its tasks. McWenderlich, a live-updating food-ordering app for a restaurant, will process the incoming orders by converting prices from USD to Indian Rupees (INR). The app can also attach additional side orders to a main order. Along the way you’ll learn:

Prerequisite: This tutorial assumes you are already familiar with the basics of Android development. If you are completely new to Android development, read through our Beginner Android Series. Other prerequisites include basic knowledge of threads and multi-threading.

Getting Started

As a first step, download the materials for this tutorial by clicking the Download materials button at the top or bottom of the tutorial. Once you’ve downloaded the materials, open the starter project in Android Studio 3.1.3 or later and get familiar with its content.

You should see:

  • FoodAdapter: An adapter class for the RecyclerView of different food orders containing the order’s name, price in INR and the side order.
  • FoodOrder: A model class for ordered food with three variables: foodName, foodPrice and sideOrder.
  • FoodRunnable: A Runnable implementation, which will allow the creation of orders to send to a background thread, which will do the processing operations.
  • MainActivity: An Activity where you have live updating food orders with their respective details.

Build and run the project. You’ll see a blank screen, which for now just shows an empty RecyclerView:

This isn’t what you want. The UI should display the new orders as soon as our hungry customers create them. You need to manage orders in the background and display them in the list. This is a typical problem that HandlerThread can solve. But before implementing this, you’ll dive into the theory of HandlerThread.

HandlerThread and its Components

In Android, the HaMeR framework allows for interaction between any pair of threads, which can be any background thread and the main thread. In order to avoid race conditions and deadlocks, a thread can execute operations simply reading messages from a queue and executing them in sequence. When thread A wants to interact with thread B, thread A puts a message into the related queue. This can be done using a Handler object.

In more detail, the actors of this framework are:

  • Handler: This is the most important object of the framework because of its double responsibility. It’s bound to a specific Looper for a given thread, and it provides the methods for sending messages to the related MessageQueue. The Handler is also responsible for the actual execution of the Message content.
  • Message: Encapsulates the information about the operation you want to execute on a specific thread.
  • Runnable: Interface that abstracts any operation that a thread can execute.
  • MessageQueue: As the name suggests, this represents the queue of messages that a thread consumes.
  • Looper: This is the object responsible for the loop that checks the MessageQueue to see if there is message to run and send it to a specific Handler. Every thread can have only one Looper.

.

But what’s the role of the HandlerThread class in all of this? As you’ll soon see, it’s a class that simplifies the creation of a Looper and related Handler instances in the case of background threads.

Now, time to start writing code!

Creating Your Own HandlerThread

In Android Studio, right-click on the com.raywenderlich.mcwenderlich root package folder and select New ▸ Kotlin File/Class. Next, name it OrderHandlerThread and select Class for Kind. Make it extend HandlerThread.

The constructor of HandlerThread requires a String as parameter. This String parameter is a name you can use to identify the thread during the debug. You can use the name of the class itself as the identifier, getting something like this:


class OrderHandlerThread : HandlerThread("OrderHandlerThread") {
}

Now, add a UiHandler property to the primary constructor, which is a handler that corresponds to your main thread. This class handles UI changes in MainActivity based on the messages it receives. This class is available for you in the MainActivity class. OrderHandlerThread should now look like this:


class OrderHandlerThread(private var uiHandler: MainActivity.UiHandler) :
    HandlerThread("OrderHandlerThread") {
}

Add two variable declarations, for a Handler and a random value, to the OrderHandlerThread class:


class OrderHandlerThread(private var uiHandler: MainActivity.UiHandler) :
    HandlerThread("OrderHandlerThread") {

  private var handler: Handler? = null
  private val random = Random()
}

Now, add the following two utility methods to this class. The first method convertCurrency will help you convert the currency of foodPrice:


/**
 * Converts the food price from USD to Indian Rupees (INR).
 * 1 USD has been considered as equal to 68.45 INR.
 * @foodPriceInDollars price of the food in USD.
 */
private fun convertCurrency(foodPriceInDollars: Float): Float {
  return foodPriceInDollars * 68.45f
}

The second method attachSideOrder attaches additional random side to each of the upcoming orders:


/**
 * Attaches random side order to the incoming food orders.
 */
private fun attachSideOrder(): String {
  val randomOrder = random.nextInt(3)
  return when (randomOrder) {
    0 -> "Chips"
    1 -> "Salad"
    else -> "Nachos"
  }
}

You need to create three more methods in the OrderHandlerThread class:

  • getHandler prepares the Handler object bound to the corresponding OrderHandlerThread, which you will use to perform the actual operations of converting the currency and attaching a side order to the upcoming foodOrder.
  • sendOrder gets the corresponding foodOrder and passes it to the Handler for operations.
  • onLooperPrepared helps to setup the handler with a looper.

Let’s take a look at those now.

Creating a Handler Bound to OrderHandlerThread

Go ahead and add the first of these methods:


private fun getHandler(looper: Looper): Handler {
 //1
 return object:Handler(looper) {
   //2
   override fun handleMessage(msg: Message?) {
     super.handleMessage(msg)
     //3
     val foodOrder = msg?.obj as FoodOrder
     //4
     foodOrder.foodPrice = convertCurrency(foodOrder.foodPrice)
     //5
     foodOrder.sideOrder = attachSideOrder()
     //6
     val processedMessage = Message()
     //7
     processedMessage.obj = foodOrder
     //8
     uiHandler.sendMessage(processedMessage)
    }
  }
}

You declared a method getHandler() and passed a looper in as its parameter. Here’s more specifically what’s happening in the above code snippet of getHandler():

  1. Creates and returns a Handler object and passes the Looper through as its parameter.
  2. The Handler overrides a method called handleMessage(), where you can handle the upcoming orders or simply the Message, process them and then send them back to the UiHandler for updating the UI.
  3. You obtain the upcoming order from the Handler.
  4. Now, it’s time to process the upcoming orders. In this line, you simply call the convertCurrency() method to get the corresponding price of food in INR.
  5. Next, you attach the additional orders to the foodOrder. You simply call the attachSideOrder() method to provide a random sideOrder with the food.
  6. After processing the foodOrder, you are ready to send it back to the UI. In this line, you create a Message to wrap the processed order.
  7. Here, you put the processed foodOrder in the object of type Message.
  8. Now, you finally send the processedMessage to the UIHandler by calling sendMessage() on its instance. This method will pass the processed messages to the UI and will, in turn, handle those messages by updating it.

Sending Messages With the Handler

Now, add the following code for sendOrder() in OrderHandlerThread:


fun sendOrder(foodOrder: FoodOrder) {
  //1
  val message = Message()
  //2
  message.obj = foodOrder
  //3
  handler.sendMessage(message)
}

Breaking down the above code snippet:

  1. Here, you create a new object of Message.
  2. You wrap the food order to the Message instance, which you created in previous step.
  3. Now, as your message object is ready, you will send this object to handler with the sendMessage() method of Handler.

Setting Up the Created Handler

Next, override the onLooperPrepared() method in OrderHandlerThread. This callback is invoked before the Looper of OrderHandlerThread starts looping the messages. You will use this callback to set up the handler that you prepared previously through getHandler().

You can override this method by pressing Control + O (same for Mac and PC) and select the onLooperPrepared() method from the list as shown below:

Update the method to look as follows:


 override fun onLooperPrepared() {
    super.onLooperPrepared()
    handler = getHandler(looper)
}

In this method, the looper of the OrderHandlerThread is ready to be passed to your handler. You initialize the handler with the getHandler() method created above.

Finally, you have completed setting up by creating your HandlerThread. Now, it’s time to manage the Runnable instances and update the UI.

Managing Runnable and Updating UI

In the FoodRunnable class, you will complete two things: adding the method setMaxOrders() and also the code in the run() method of the Runnable. The setMaxOrders() method will create the orders of certain sizes, passed as parameters.

First, declare an instance of OrderHandlerThread in the primary constructor of the FoodRunnable. It should look as follows:


class FoodRunnable(private var orderHandlerThread: OrderHandlerThread)
    : Runnable {


}

Now, add the following piece of code for the setMaxOrders method:


fun setMaxOrders(size: Int) {
  this.size = size
}

The above assigns the number of orders created from MainActivity.

Now, complete the run() method by adding the following code:


override fun run() {
  alive = true
  while (alive && count < size) {
    count++
    //1
    val foodName = getRandomOrderName()
    //2
    val foodPrice = getRandomOrderPrice()
    //3
    orderHandlerThread.sendOrder(FoodOrder(foodName,foodPrice))
    //4
    try {
      Thread.sleep(1000)
    } catch (exception: InterruptedException) {
      exception.printStackTrace()
    }
  }
}

The thread will run until the counter count is equal to the size of orders required.

Here’s what happening in the above code:

  1. You get a random food name from the method getRandomOrderName(). The method is already declared in the FoodRunnable class.
  2. You get a random value of price for the order from the method getRandomPrice(), which is already declared in the FoodRunnable class.
  3. You then pass the model to the sendOrder() method of the orderHandlerThread object.
  4. You put a delay of one second by calling Thread.sleep() so that you can see the changes in the UI when new items are added to the RecyclerView.

You have completed managing your Runnable to register, send and create your orders. Next, you will update the UI of MainActivity. Tighten your seatbelt!

Updating UI and Other Initializations

Open MainActivity in the com.raywenderlich.mcwenderlich package. You will observe that the RecyclerView methods and the adapter are already set up for you in onCreate(). The code of the UI handler has also been set up for you. This UiHandler is bound to the UI of the Activity and, thus, can be used to change the UI.

Note: This is done to ensure that UI changes are done from the UI (i.e., main) thread. Changing UI from a separate worker thread could produce an ANR (Application not Responding) dialog.

Next, you will declare OrderHandlerThread and FoodRunnable properties. You will start them in the onStart() method of the activity. Add the following declarations to the MainActivity class:


private lateinit var foodRunnable: FoodRunnable
private lateinit var orderHandlerThread: OrderHandlerThread

Now override the onStart() method and update it to be:


override fun onStart() {
  super.onStart()
  //1
  orderHandlerThread = OrderHandlerThread(uiHandler)
  //2
  orderHandlerThread.start()
  //3
  foodRunnable = FoodRunnable(orderHandlerThread)
  //4
  foodRunnable.setMaxOrders(10)
  foodRunnable.start()
}

Here’s what happening in above code snippet:

  1. You initialize the orderHandlerThread and pass the uiHandler as parameter.
  2. You start the orderHandlerThread by using the start() method.
  3. You initialize the FoodRunnable, passing the instance orderHandlerThread.
  4. Finally, you invoke the setMaxOrders() method passing 10 as the size of the orders, and you then start the Runnable.

Next, you will update the UI by using the UiHandler. Update the nested UiHandler class in MainActivity to look like so:


class UiHandler : Handler() {

  private lateinit var weakRefFoodListAdapter: WeakReference<FoodListAdapter>

  private lateinit var weakRefOrderRecyclerView: WeakReference<RecyclerView>

  fun setAdapter(foodListAdapter: FoodListAdapter) {
    weakRefFoodListAdapter = WeakReference(foodListAdapter)
  }

  fun setRecyclerView(foodRecyclerView: RecyclerView) {
    weakRefOrderRecyclerView = WeakReference(foodRecyclerView)
  }

  private fun addAndNotifyForOrder(foodOrder: FoodOrder, position: Int) {
    weakRefFoodListAdapter.get()?.getOrderList()?.add(foodOrder)
    weakRefOrderRecyclerView.get()?.adapter?.notifyItemInserted(position)
  }

  override fun handleMessage(msg: Message?) {
    super.handleMessage(msg)
    //1
    val position = weakRefFoodListAdapter.get()?.getOrderList()?.size
    //2
    addAndNotifyForOrder(msg?.obj as FoodOrder, position!!)
    //3
    weakRefOrderRecyclerView.get()?.smoothScrollToPosition(weakRefFoodListAdapter
        .get()?.itemCount!!)
   }
  }
}

Note: Here, it’s important to recognize that you are using the WeakReference class for operating on the foodOrder instances. This is to ensure that these views are garbage collected successfully and don’t cause any type of memory leaks.

Here’s what happening in above code snippet:

  1. You get the current size of the orders from the weak reference of the adapter.
  2. In the function addAndNotifyForOrder(), you add the new upcoming order from the OrderHandlerThread and notify the adapter of adding the new item at the position.
  3. You smoothly scroll to the current position of the list where orders have been added.

Cleaning Up

To clean up, you will add some code in onDestroy() of MainActivity. This code will ensure that the orderHandlerThread and the foodRunnable stop properly when the Activity is destroyed. Override the onDestroy() method of MainActivity with the following code:


override fun onDestroy() {
  super.onDestroy()
  foodRunnable.stop()
  orderHandlerThread.quit()
}

The above code stops the foodRunnable and the orderHandlerThread by calling stop() and quit() respectively on their instances.

And that’s all you need to assemble for your HandlerThread!

Build and run your app. You will see the following:

Summary of Interaction Between Threads

Here’s a quick recap of what happens throughout the entire app. The MainActivity creates 10 orders through the FoodRunnable. The OrderHandlerThread receives the orders, converts their prices from USD to INR, and attaches the additional side orders to them. Finally, OrderHandlerThread forwards the processed orders back to the UI through the UiHandler, which is bound to the UI of the app. This UiHandler updates the item list in the FoodListAdapter and then notifies the RecyclerView adapter.

Use Cases of HandlerThread

Consider some use cases of HandlerThread, which you can use in your day-to-day Android development:

  • They are useful when you need to implement a Producer/Consumer paradigm between different threads.
  • It simplifies the creation of the Looper instance that is bound to a specific thread.
  • It is used in the Android Service implementation in order to execute some tasks off of the main thread.

Comparison With Other Options

Now, compare HandlerThread with other related options for doing concurrent programming in Android:

  • AsyncTask: This is a Facade that simplifies the usage of the HaMeR framework components when you have to run simple tasks in the background and produce a result to be displayed in the UI.
  • Service: Activities are not a good place for threads because they can be killed by the system if it needs resources. On the other hand, Services are components that Android preserves with higher priority and are a better place for a thread to run. Because Service run on the main thread, a HandlerThread is often a good solution.
  • IntentService: This is a Service implementation that creates a HandlerThread in order to run tasks sequentially. This is useful if you need to run tasks using the fire-and-forget paradigm.

Note: It’s important to recognize here that IntentService uses HandlerThread internally. You can look at the source code here.

Where to Go From Here?

Good Job! Now, it’s time to look back to what you’ve learned in this journey:

  • What HandlerThread is and its role in the HaMeR architecture.
  • How to create your own HandlerThread implementation.
  • How to manage UI changes from a background thread.
  • Use cases of HandlerThread.
  • Differences between HandlerThread and other classes like AsyncTask, Service and IntentService.

You can download the final project by using the Download materials button at the top or bottom of this tutorial.

If you want to learn more about HandlerThread, look into the Android Documentation.

You can also learn more about concurrency on Android by checking out our Android Background Processing video course.

I hope you enjoyed this tutorial on HandlerThread. Join us in the forums to discuss this tutorial and your findings as you work with HandlerThread!

LEAVE A REPLY

Please enter your comment!
Please enter your name here