The
Handler
class is fundamental to the infrastructure of Android apps—together with Looper
. It underpins everything that the main thread does—including the invocation of the Activity
lifecycle methods.
While Looper
takes care of dispatching work on its message-loop thread, Handler
provides the means to add work to the message queue belonging to a Looper
.
We can create a Handler
to submit work to be processed on the main thread simply by creating a new instance of Handler
from an Activity
lifecycle method such as onCreate
, shown as follows:
protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Handler handler = new Handler(); // … }
We could also be explicit that we want to submit work to the main thread by passing a reference to the main Looper
instance into the Handler
constructor.
Handler handler = new Handler(Looper.getMainLooper());
Exactly what we mean by "work" can be described by subclasses of java.lang.Runnable
, or instances of android.os.Message
. We can post Runnables to a Handler
instance or send Messages to it, and it will add them to the queue belonging to the associated Looper
instance.
We can post work to a Handler
very simply, for example, by creating an anonymous inner Runnable
:
handler.post(new Runnable(){ public void run() { // do some work on the main thread. } });
The Looper
instance to which the Handler
is bound works its way through the queue, executing each Runnable
as soon as possible. Posting with the post
method simply adds a new Runnable
at the end of the queue.
If we want our Runnable
to take priority over anything currently in the queue, we can post it to the front of the queue ahead of existing work:
handler.postAtFrontOfQueue(new Runnable(){
public void run() {
// do some work on the main thread.
}
});
In a single-threaded application it might seem like there is not a whole lot to be gained from posting work to the main thread like this, but breaking things down into small tasks that can be interleaved and potentially reordered is very useful for maintaining responsiveness.
This becomes clear if we consider the question: what if we want to schedule some work in 10 seconds time? One way to do this would be to Thread.sleep
the main thread for 10 seconds, but that would mean the main thread can't do anything else in the meantime. Also, remember that we must never block the main thread! The alternative is to post a delayed Runnable
to the Handler
.
handler.postDelayed(new Runnable(){
public void run() {
// do some work on the main thread
// in 10 seconds time
}
}, TimeUnit.SECONDS.toMillis(10));
We can still post additional work for execution in the meantime, and our delayed Runnable
instance will execute after the specified delay. Note that we're using the TimeUnit
class from the java.lang.concurrent
package to convert seconds to milliseconds.
A further scheduling option for posted work is postAtTime
, which schedules Runnable
to execute at a particular time relative to the system uptime (how long it has been since the system booted):
handler.postAtTime(new Runnable(){ public void run() { // … do some work on the main thread } }, SystemClock.uptimeMillis() + TimeUnit.SECONDS.toMillis(10));
Since postDelayed
is implemented in terms of an offset from the SystemClock
uptime, it is usually easier to just use postDelayed
.
Since we instantiated our Handler
on the main thread, all work submitted to it executes on the main thread. This means that we must not submit long running operations to this particular Handler
, but we can safely interact with the user interface:
handler.post(new Runnable(){ public void run() { TextView text = (TextView) findViewById(R.id.text); text.setText("updated on the UI thread"); } });
This applies regardless of which thread posts the Runnable
, which makes Handler
an ideal way to send the results of work performed by other threads to the main thread.
Thread thread = new Thread(){ public void run(){ final BigInteger result = calculatePrime(500); handler.post(new Runnable(){ public void run(){ TextView text = (TextView) findViewById(R.id.text); text.setText(result.toString()); } }); } }; thread.setPriority(Thread.MIN_PRIORITY); thread.start();
Handler
is so fundamental that it is integrated right into the View
class hierarchy, so we can rewrite the last example as follows:
final TextView text = (TextView) findViewById(R.id.text); Thread thread = new Thread(){ public void run(){ final BigInteger result = calculatePrime(500); text.post(new Runnable(){ public void run() { text.setText(result.toString()); } }); } }; thread.setPriority(Thread.MIN_PRIORITY); thread.start();
For comparison, both of the previous examples are roughly equivalent to the following AsyncTask
code:
new AsyncTask<Void,Void,BigInteger>(){ public BigInteger doInBackground(Void… params){ return calculatePrime(500); } public void onPostExecute(BigInteger result){ TextView text = (TextView) findViewById(R.id.text); text.setText(result.toString()); } }.execute();
When writing code in an Activity
class, there is an alternative way of executing a Runnable on the main thread using the runOnUiThread
method of Activity
. If invoked from a background thread, the Runnable
will be posted to a Handler
instance attached to the main thread. If invoked from the main thread, the Runnable
will be executed immediately.
We can cancel a pending operation by removing a posted Runnable
callback from the queue.
final Runnable runnable = new Runnable(){
public void run() {
// … do some work
}
};
handler.postDelayed(runnable, TimeUnit.SECONDS.toMillis(10));
Button cancel = (Button) findViewById(R.id.cancel);
cancel.setOnClickListener(new OnClickListener(){
public void onClick(View v) {
handler.removeCallbacks(runnable);
}
});
Notice that in order to be able to specify what to remove, we must keep a reference to the Runnable
instance, and that cancellation applies only to pending tasks—it does not attempt to stop a Runnable
that is already mid-execution.
When we post a Runnable
, we can—as seen in the previous examples—define the work at the call site with an anonymous Runnable
. As such, the Handler
does not know in advance what kind of work it might be asked to perform.
If we often need to perform the same work from different call sites we could define a static or top-level Runnable
class that we can instantiate from different call sites.
Alternatively, we can turn the approach on its head by sending Messages to a Handler
, and defining the Handler
to react appropriately to different Messages.
Taking a simple example, let's say we want our Handler
to display hello
or goodbye
, depending on what type of Message
it receives. To do that, we'll extend Handler
and override its
handleMessage
method.
public static class SpeakHandler extends Handler { public static final int SAY_HELLO = 0; public static final int SAY_BYE = 1; @Override public void handleMessage(Message msg) { switch(msg.what) { case SAY_HELLO: sayWord("hello"); break; case SAY_BYE: sayWord("goodbye"); break; default: super.handleMessage(msg); } } private void sayWord(String word) { … } }
Here we've implemented the handleMessage
method to expect messages with two different what
values and react accordingly.
To attach an instance of our Handler
to the main thread, we simply instantiate it from any method which runs on the main thread:
private Handler handler; protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); handler = new SpeakHandler(); // … }
Remember that we can send Messages to this Handler
from any thread, and they will be processed by the main thread. We send Messages to our Handler
as shown:
handler.sendEmptyMessage(SAY_HELLO);
Messages may also carry an object payload as context for the execution of the message. Let's extend our example to allow our Handler
to say any word that the message sender wants:
public static class SpeakHandler extends Handler { public static final int SAY_HELLO = 0; public static final int SAY_BYE = 1; public static final int SAY_WORD = 2; @Override public void handleMessage(Message msg) { switch(msg.what) { case SAY_HELLO: sayWord("hello"); break; case SAY_BYE: sayWord("goodbye"); break; case SAY_WORD: sayWord((String)msg.obj); break; default: super.handleMessage(msg); } } private void sayWord(String word) { … } }
Within our handleMessage
method, we can access the payload of the Message
directly by accessing the public obj
property. The Message
payload can be set easily via alternative static obtain
methods.
handler.sendMessage( Message.obtain(handler, SpeakHandler.SAY_WORD, "tada!"));
While it should be quite clear what this code is doing, you might be wondering why we didn't create a new instance of Message
by invoking its constructor, and instead invoked its static method obtain
.
The reason is efficiency. Messages are used only briefly—we instantiate, dispatch, handle, and then discard them. So if we create new instances for each we are creating work for the garbage collector.
Garbage collection is expensive, and the Android platform goes out of its way to minimize object allocation whenever it can. While we can instantiate a new Message
object if we wish, the recommended approach is to obtain one, which re-uses Message
instances from a pool and cuts down on garbage collection overhead.
Just as we can schedule Runnables with the variants of the post method, we can schedule Messages with variants of send
.
handler.sendMessageAtFrontOfQueue(msg); handler.sendMessageAtTime(msg, time); handler.sendMessageDelayed(msg, delay);
There are also empty-message variants for convenience, when we don't have a payload.
handler.sendEmptyMessageAtTime(what, time); handler.sendEmptyMessageDelayed(what, delay);
Canceling sent Messages is also possible, and actually easier than canceling posted Runnables because we don't have to keep a reference to the Messages that we might want to cancel—instead we can just cancel Messages by their what
value.
handler.removeMessages(SpeakHandler.SAY_WORD);
Note that just as with posted Runnables, Message
cancellation only removes pending operations from the queue—it does not attempt to stop an operation which is already being executed.
So far we subclassed Handler
to override its handleMessage
method, but that isn't our only option. We can prefer composition over inheritance by passing an instance of Handler.Callback
during Handler
construction.
public static class Speaker implements Handler.Callback { public static final int SAY_HELLO = 0; public static final int SAY_BYE = 1; public static final int SAY_WORD = 2; @Override public boolean handleMessage(Message msg) { switch(msg.what) { case SAY_HELLO: sayWord("hello"); break; case SAY_BYE: sayWord("goodbye"); break; case SAY_WORD: sayWord((String)msg.obj); break; default: return false; } return true; } private void sayWord(String word) { … } }
Notice that the signature of handleMessage
is slightly different here—we must return a boolean
indicating whether or not the Message
was handled. To create a Handler
that uses our Callback
, simply pass the Callback
during Handler
construction.
Handler handler = new Handler(new Speaker());
If we return false
from the handleMessage
method of our Callback
, the Handler
will invoke its own handleMessage
method, so we could choose to use a combination of inheritance and composition to implement default behavior in a Handler
subclass, and mix in special behavior by passing in an instance of Handler.Callback
.
public static class SpeakHandler extends Handler { public static final int SAY_HELLO = 0; public static final int SAY_BYE = 1; public SpeakHandler(Callback callback) { super(callback); } @Override public void handleMessage(Message msg) { switch(msg.what) { case SAY_HELLO: sayWord("hello"); break; case SAY_BYE: sayWord("goodbye"); break; default: super.handleMessage(msg); } } private void sayWord(String word) { … } } public static class Speaker implements Handler.Callback { public static final int SAY_WORD = 2; @Override public boolean handleMessage(Message msg) { if (msg.what == SAY_WORD) { sayWord((String)msg.obj); return true; } return false; } private void sayWord(String word) { … } } Handler h = new SpeakHandler(new Speaker()); h.sendMessage(Message.obtain(handler, Speaker.SAY_WORD, "!?"));
With SpeakHandler
set up like this, we can easily send Messages from any thread to update the user interface. Sending from a background thread or the main thread itself is exactly the same—just obtain a Message
and send it via SpeakHandler
.
Let's extend our example to bind the app to a network socket and echo lines of text it receives from the socket to the screen. Listening for lines of text from the network socket is a blocking operation, so we must not do it from the main thread.
We'll start a background thread, then bind the server socket and wait for a client to connect over the network. When this background thread receives text from the socket, it will send it in a Message
to the SpeakHandler
instance, which will update the user interface on the main thread.
static class Parrot extends Thread { private Handler handler; private InetAddress address; private ServerSocket server; public Parrot(InetAddress address, Handler handler) { this.handler = handler; this.address = address; setPriority(Thread.MIN_PRIORITY); } public void run() { try { server = new ServerSocket(4444, 1, address); while (true) { Socket client = server.accept(); handler.sendMessage(Message.obtain(handler, SpeakHandler.SAY_HELLO)); BufferedReader in = new BufferedReader( new InputStreamReader( client.getInputStream())); String word; while (!"bye".equals(word = in.readLine())) { handler.sendMessage(Message.obtain(handler, SpeakHandler.SAY_WORD, word)); } client.close(); handler.sendMessage( Message.obtain( handler, SpeakHandler.SAY_BYE)); } } catch (Exception exc) { Log.e(TAG, exc.getMessage(), exc); } } }
Let's have a quick look at the key elements of the run
method. First, we bind a socket to port 4444 (any port higher than 1024 will do) on the given address, which allows us to listen for incoming connections. The second parameter says we're only allowing one connection at a time:
server = new ServerSocket(4444, 1, address);
We loop forever, or until an exception is thrown. Inside the loop we wait for a client to connect:
Socket client = server.accept();
The accept
method blocks, so our background thread is suspended until a client makes a connection. As soon as a client connects, we send a SAY_HELLO
message to our Handler
, so the main thread will update the user interface for us:
handler.sendMessage( Message.obtain(handler, SpeakHandler.SAY_HELLO));
Next, we wrap a buffering reader around our socket's input stream, and loop on its readLine
method, which blocks until a line of text is available.
BufferedReader in = new BufferedReader( new InputStreamReader(client.getInputStream())); String word; while (!"bye".equals(word = in.readLine())) {
When we receive some text from the socket, we send it in a Message
to our Handler
.
handler.sendMessage( Message.obtain(handler, SpeakHandler.SAY_WORD, word));
If we receive bye
, we'll break out of the loop, disconnect this client, and say goodbye:
client.close(); handler.sendMessage( Message.obtain(handler, SpeakHandler.SAY_BYE));
To obtain the address of the device, we can use the Wi-Fi service. We'll need to convert the value we get from the Wi-Fi service to an instance of the InetAddress
class, elided here for brevity.
private InetAddress getAddress() { WifiManager wm = (WifiManager)getSystemService(WIFI_SERVICE); return asInetAddress(wm.getConnectionInfo().getIpAddress()); }
Binding the socket and using the Wi-Fi service requires permissions to be requested in the Android manifest:
<uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
Finally we need to create and start Parrot
in a suitable Activity
lifecycle method, for example, onResume
. To make it easy to connect, we'll display the address and port on the screen.
TextView view = (TextView) findViewById(R.id.speak); if (parrot == null) { InetAddress address = getAddress(); parrot = new Parrot(address, handler); parrot.start(); view.setText("telnet " + address.getHostAddress() + " 4444"); }
We should also remove pending messages in onPause
, and disconnect the socket if the Activity
is finishing—the complete code is available for download from the accompanying website.
When the app starts, you'll see a message on the device screen like telnet 192.168.0.4 4444. To connect and send messages to your device's screen, open the command prompt on your development computer and copy the text from your device screen to the command prompt.
You should see the following output:
Trying 192.168.0.4... Connected to 192.168.0.4. Escape character is '^]'.
Congratulations! You're connected. Enter some words in the command prompt and they'll appear on your device screen. Enter bye
to disconnect.
It is worth spending a few moments to consider the difference between posting Runnables and sending Messages.
The runtime difference mostly comes down to efficiency. Creating new instances of Runnable
each time we want our Handler
to do something adds garbage collection overhead, while sending messages re-uses Message
instances, which are sourced from an application-wide pool.
The difference at development time is between allowing code at the call site to specify arbitrary work for the Handler
to do (Runnable.run)
potentially spreading similar code throughout the codebase, versus the Handler
defining the work it is prepared to do in a single place (Handler.handleMessage
).
For prototyping and small one-offs, posting Runnables is quick and easy, while the advantages of sending Messages tend to grow with the size of the application. It should be said that Message
sending is more "The Android Way", and is used throughout the platform to keep garbage to a minimum and apps running smoothly.
So far we only really considered Handler
as a way to request work be performed on the main thread—we've submitted work from the main thread to itself, and from a background thread to the main thread.
In fact we can bind Handlers to any thread we create, and in doing so allow any thread to submit work for another thread to execute. For example, we can submit work from the main thread to a background thread or from one background thread to another.
We saw one way of setting up a Looper
thread with a Handler
in the Understanding Looper section earlier in this chapter, but there's an easier way using a class provided by the SDK for exactly this purpose, android.os.HandlerThread
.
When we create a HandlerThread
, we specify two things: a name for the thread, which can be helpful when debugging; and its priority, which must be selected from the set of static values in the android.os.Process
class.
HandlerThread thread = new HandlerThread("bg", Process.THREAD_PRIORITY_BACKGROUND);
Thread priorities in Android are mapped onto Linux "nice" levels, which govern how often a thread gets to run.
In addition to prioritization, Android limits CPU resources using Linux cgroups. Threads that are of background priority are moved into the bg_non_interactive
cgroup, which is limited to 5 percent of available CPU if threads in other groups are busy.
Adding THREAD_PRIORITY_MORE_FAVORABLE
to THREAD_PRIORITY_BACKGROUND
when configuring your HandlerThread
moves the thread into the default cgroup, but always consider whether it is really necessary—it often isn't!
HandlerThread
extends java.lang.Thread
, and we must start()
it before it will actually begin processing its queue:
thread.start();
Now we need a Handler
through which we can pass work to our HandlerThread
. So we create a new instance, but this time we parameterize the constructor with the Looper
associated with our HandlerThread
.
Handler handler = new Handler(thread.getLooper());
That's all there is to it—we can now post Runnables for our HandlerThread
to execute:
handler.post(new Runnable(){ public void run() { // ... do some work in the background thread } });
To send Messages to our HandlerThread
, we'll need to override handleMessage
or provide a Callback
via the alternate Handler
constructor:
Handler.Callback callback = new Handler.Callback(){ … }; Handler handler = new Handler(thread.getLooper(), callback);
If we create a HandlerThread
to do background work for a specific Activity
, we will want to tie the HandlerThread
's lifecycle closely to that of the Activity
to prevent resource leaks.
A HandlerThread
can be shut down by invoking quit
, which will stop the HandlerThread
from processing any more work from its queue. A quitSafely
method was added at API level 18, which causes the HandlerThread
to process all remaining tasks before shutting down. Once a HandlerThread
has been told to shut down, it will not accept any further tasks.
Just as we did in the previous chapter with AsyncTask
, we can use the Activity
lifecycle methods to determine when we should quit the HandlerThread
.
private HandlerThread thread; protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); thread = new HandlerThread( … ); } protected void onPause() { super.onPause(); if ((thread != null) && (isFinishing())) thread.quit(); }
3.15.25.186