Looper, Handler in Android Part 3 - Handler
This article is the third in a series on Looper and Handler in Android. To fully understand the concepts discussed here, it's assumed that you have read or are already familiar with the concepts covered in part1 and part2. If not, I recommend visiting those articles first.
In this article, we will explore deeper into the post()
method of Handler
, with an example. By the end of this article, you would have a clear understanding of how the post()
method executes your code in a different thread.
How to use Handler
There are two main way Handler
can be used in programming:
- Adding or posting operation to be executed directly inside the thread from another thread. This flow is invoked by the
post()
method ofHandler
. - Sending a message about the operation which needs to be done inside the thread from another thread. This flow is invoked by a combination of the
sendMessage()
andhandleMessage()
methods ofHandler
.
When to use post()
The post()
method is particularly useful when you want to quickly run a block of code in another thread. For example, if you are running some computationally intensive operation in a background thread but want to post updates to the UI, then you can use a Handler
attached to the MainThread
for same.
// Example code demonstrating the usage of post() method
public class ThreadWithLongRunningOperation extends Thread {
@Override
public void run() {
// Some long running operation
// Post intermediate progress to UI
Handler mainThreadHandler = new Handler(Looper.getMainLooper());
mainThreadHandler.post(new Runnable() {
@Override
public void run() {
// update UI
}
});
// Continue operation
// Update UI to show completion (here, runnable is posted using lambda)
mainThreadHandler.post(() -> {
// Update UI
});
}
}
Inner working of post()
The signature of the post method is
public final boolean post (Runnable r)
From part 2, we know that Handler
is used to interact with the Message
loop, and Looper
only operates on the Message
object. So, post()
will first convert the Runnable
to a Message
and add it to the queue as shown below AOSP code snippet of Handler.java
:
// Method to convert Runnable to Message
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}
/**
* Causes the Runnable r to be added to the message queue.
* The runnable will be run on the thread to which this handler is
* attached.
*/
public final boolean post(@NonNull Runnable r) {
return sendMessageDelayed(getPostMessage(r), 0);
}
/**
* Enqueue a message into the message queue after all pending messages
* before (current time + delayMillis). You will receive it in
* {@link #handleMessage}, in the thread attached to this handler.
*/
public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
/**
* Enqueue a message into the message queue after all pending messages
* before the absolute time (in milliseconds) <var>uptimeMillis</var>.
* <b>The time-base is {@link android.os.SystemClock#uptimeMillis}.</b>
* Time spent in deep sleep will add an additional delay to execution.
* You will receive it in {@link #handleMessage}, in the thread attached
* to this handler.
*/
public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
MessageQueue queue = mQueue;
if (queue == null) {
RuntimeException e = new RuntimeException(
this + " sendMessageAtTime() called with no mQueue");
Log.w("Looper", e.getMessage(), e);
return false;
}
return enqueueMessage(queue, msg, uptimeMillis);
}
Note: You can also use
Handler
to post block of code to execute on same or another thread with some delay usingpostDelayed
method. If you want to execute theRunnable
after some time instead of immediately you can replacepost
withpostDelayed
. UsingpostDelayed
will callsendMessageDelayed
method with the delay provided in the parameter instead of 0 as shown above.
Finally, the runnable is executed inside dispatchMessage()
from the loop()
method we saw in part2.
public void dispatchMessage(@NonNull Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
// Handle other types of messages
}
}
private static void handleCallback(Message message) {
message.callback.run();
}
Adding these new pieces to our sequence diagram from part 2, results in below diagram
Example
Now, let us look at an example where we will use the post()
method. Here, we will post()
the progress of some long running operation in the main thread which will update the UI. This is similar to how we used to update UI inside AsyncTask
's onProgressUpdate
. As AsyncTask
is now deprecated, this is a suitable alternative.
First, let us update the ThreadWithLongRunningOperation
class as below
public class ThreadWithLongRunningOperation extends Thread {
private final Consumer<Integer> mOnThreadProgressConsumer;
public ThreadWithLongRunningOperation(Consumer<Integer> onThreadProgressConsumer) {
this.mOnThreadProgressConsumer = onThreadProgressConsumer;
}
@Override
public void run() {
// Dummy sleep value to show work is happening
final long SLEEP_TIME_MS = 1000;
// Create a handler using the Main thread looper
Handler mainThreadHandler = new Handler(Looper.getMainLooper());
// Post to the main handler; this will run the Runnable code block in UI thread
mainThreadHandler.post(() -> mOnThreadProgressConsumer.accept(0));
// Some long-running operation; using sleep to simulate the same
trySleep(SLEEP_TIME_MS);
// Post intermediate progress to UI
mainThreadHandler.post(() -> mOnThreadProgressConsumer.accept(50));
// Continue operation; using sleep to simulate the same
trySleep(SLEEP_TIME_MS);
// Update UI to show completion
mainThreadHandler.post(() -> mOnThreadProgressConsumer.accept(100));
}
private void trySleep(long millis) {
try {
sleep(millis);
} catch (InterruptedException e) {
// do nothing
}
}
}
Note: If you want to learn about the
Consumer
interface, I have posted an article here.
In the activity_main.xml
layout we will use the TextView
to show the progress:
<TextView
android:id="@+id/counter_tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="0"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
Finally, in our MainActivity.java
, we will start the Thread
and consume the update as follows:
public class MainActivity extends AppCompatActivity {
TextView mCounterTextView;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mCounterTextView = findViewById(R.id.counter_tv);
}
@Override
protected void onResume() {
super.onResume();
ThreadWithLongRunningOperation threadWithLongRunningOperation
= new ThreadWithLongRunningOperation(
(progress) -> mCounterTextView.setText(String.valueOf(progress))
);
threadWithLongRunningOperation.start();
}
}
Example explanation
The provided code features a custom thread, ThreadWithLongRunningOperation
, which executes a simulated long-running operation and communicates progress updates to the UI thread through a Handler
. The Handler
, associated with the main thread's Looper
, posts UI updates using the post()
method to be executed inside the UIThread
.
In the MainActivity, an instance of this custom thread is created and started in the onResume
method, with progress updates displayed in a TextView
. The Handler ensures proper communication between the background thread and the UI thread, allowing smooth progress updates in response to the long-running operation.
Conclusion
In this article, we examined how Message
gets added to the MessageQueue
inside the Handler
class. We explored how Runnable
objects are converted to Message
and saw an example of using the post()
method to update the UI thread from another background thread.
In the next article, we will continue our exploration of the Handler
class and delve into the usage of the sendMessage()
and handleMessage()
methods.
Stay tuned for more insights into effective handler usage in Android development!
Subscribe to my newsletter
Read articles from Sajal Raj Gautam directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
Sajal Raj Gautam
Sajal Raj Gautam
I’m a software engineer with a focus on Android applications and framework development. I specialize in tackling complex challenges with innovative solutions. I’ve worked across various form factors and software layers, contributing to the various software development lifecycle—design, development, testing, planning, and performance optimization. Leading teams, I ensure quality deliverables within deadlines. Beyond Android, I’ve gained hands-on experience in Robotics, IoT devices, RTOS applications, and Machine Learning. My dynamic career journey reflects my enthusiasm for learning and adapting to new technologies. I’m eager to explore opportunities and contribute to cutting-edge developments in the tech industry.