Threading With HandlerThread in Android

You will learn how to use HandlerThread to receive messages from a Runnable in an Activity and pass them back to a UI handler to update the UI. By Amanjeet Singh.

Leave a rating/review
Download materials
Save for later
Share
You are currently viewing page 2 of 3 of this article. Click here to view the first page.

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.
Amanjeet Singh

Contributors

Amanjeet Singh

Author

Massimo Carli

Tech Editor

Sean Stewart

Illustrator

Meng Taing

Final Pass Editor

Eric Soto

Team Lead

Over 300 content creators. Join our team.