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.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Threading With HandlerThread in Android
20 mins
- Getting Started
- HandlerThread and its Components
- Creating Your Own HandlerThread
- Creating a Handler Bound to OrderHandlerThread
- Sending Messages With the Handler
- Setting Up the Created Handler
- Managing Runnable and Updating UI
- Updating UI and Other Initializations
- Cleaning Up
- Summary of Interaction Between Threads
- Use Cases of HandlerThread
- Comparison With Other Options
- Where to Go From Here?
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()
:
- Creates and returns a
Handler
object and passes theLooper
through as its parameter. - The
Handler
overrides a method calledhandleMessage()
, where you can handle the upcoming orders or simply theMessage
, process them and then send them back to the UiHandler for updating the UI. - You obtain the upcoming order from the
Handler
. - 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. - Next, you attach the additional orders to the
foodOrder
. You simply call theattachSideOrder()
method to provide a randomsideOrder
with the food. - 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. - Here, you put the processed
foodOrder
in the object of typeMessage
. - Now, you finally send the
processedMessage
to theUIHandler
by callingsendMessage()
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:
- Here, you create a new object of
Message
. - You wrap the food order to the
Message
instance, which you created in previous step. - Now, as your message object is ready, you will send this object to
handler
with thesendMessage()
method ofHandler
.
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:
- You get a random food name from the method
getRandomOrderName()
. The method is already declared in theFoodRunnable
class. - You get a random value of price for the order from the method
getRandomPrice()
, which is already declared in theFoodRunnable
class. - You then pass the model to the
sendOrder()
method of theorderHandlerThread
object. - 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 theRecyclerView
.
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.
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:
- You initialize the
orderHandlerThread
and pass theuiHandler
as parameter. - You start the
orderHandlerThread
by using thestart()
method. - You initialize the
FoodRunnable
, passing the instanceorderHandlerThread
. - Finally, you invoke the
setMaxOrders()
method passing 10 as the size of the orders, and you thenstart
theRunnable
.
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.
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:
- You get the current size of the orders from the weak reference of the adapter.
- In the function
addAndNotifyForOrder()
, you add the new upcoming order from theOrderHandlerThread
and notify the adapter of adding the new item at theposition
. - You smoothly scroll to the current position of the list where orders have been added.