- Asynchronous Android Programming(Second Edition)
- Helder Vasconcelos
- 6269字
- 2021-07-14 10:43:13
Understanding Looper
Before we can understand Looper
, we need to understand where it gets its name from.
Note
A loop is a group of instructions that are repeated continually until a termination condition is met.
Following this definition, Android's Looper
executes on a thread that has a MessageQueue
, executes a continuous loop waiting for work, and blocks when there is no work pending. When work is submitted to its queue, it dispatches it to the target Handler
defined explicitly on the Message
object.
Note
A message is a notification object containing a description and arbitrary data object that can be sent to a Handler.
The Looper on Android is an implementation of a common UI programming concept known as an event loop. Later, at the end of this processing sequence, the Handler
will process the Message
and execute your domain logic in order to solve an application user problem.
The Looper
sequence on Android follows these steps:
- Wait until a Message is retrieved from its MessageQueue
- If logging is enabled, print dispatch information
- Dispatch the message to the target Handler
- Recycle the Message
- Go to step 1
As mentioned on the previous chapter, the main thread implicitly creates its own Looper
to sequentially process all that is needed to keep the application running and to manage the interaction between the application components.
To access the main thread's Looper you want access to the main thread's Looper
instance, use the static method getMainLooper()
:
Looper mainLooper = Looper.getMainLooper();
To set up our own Looper
thread, we need to invoke two static methods of Looper
—prepare
and loop
—from within the thread, and they will handle the continuous loop. Here is a simple example:
class SimpleLooper extends Thread { public void run() { // Attach a Looper to the current Thread Looper.prepare(); // Start the message processing Looper.loop(); } }
In the snippet, when the SimpleLopper
object is created and started by invoking the start()
method, a new thread is created in the current application process, and run()
is automatically called inside the new thread. When the run()
method is called, we attach a Looper
to the current thread when we invoke the static Looper.prepare()
method. Following that, we start processing messages when loop()
is called. The prepare()
method is responsible for initializing the MessageQueue
and attaching the queue as a ThreadLocal
parameter to the current thread.
When loop()
is invoked, the run()
method will block until the looper is interrupted to process new messages be added to the queue.
Note
Looper.prepare()
must only be called once from within the same thread; otherwise, a RuntimeException
will to be thrown that says only one looper may be created per thread.
When we want to stop the continuous Looper
execution, we can either invoke its member function quit()
to stop it without processing the remaining messages in its queue or quitSafely()
to process the remaining work on the queue and stop.
Understanding Handler
Together with Looper
, the Handler
class is fundamental to the infrastructure of Android apps. 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
serves two purposes: providing an interface to submit messages to its Looper
queue, and implementing the callback for processing those messages when they are dispatched by the Looper
.
It is also import to know that each Handler
is bound to a single Looper
and, by extension, to one thread and its looper MessageQueue
.
To bind to the Looper
of the current thread, we need to instantiate it over the default Handler()
constructor after we initialize the Looper
by calling the prepare
method. Since we create our handler inside our SimpleLooper
thread over the default constructor Handler()
, myHandler
will be attached to the current thread's Looper
instead of the main thread's Looper
:
public class SimpleLooper extends Thread{ private Handler myHandler; @Override public void run() { Looper.prepare(); myHandler = new MyHandler(); Looper.loop(); } public Handler getHandler(){ return myHandler; } }
Apart from providing an interface to submit work to Looper
threads, Handler
also defines the code that process the messages submitted. In the following code, the MyHandler
class overrides the superclass' (Handler
) handleMessage
member method to define our message-handling code:
public class MyHandler extends Handler { @Override public void handleMessage(Message msg) { // Add here your message handling // processing } }
Once started, the Looper
thread will wait inside Looper.loop()
for messages to be added to its queue.
When another thread adds a Message
to the queue using the submit
method, the waiting thread will then dispatch the message to our target MyHandler
by invoking the handler's handleMessage()
method.
With the Handler
object reference in hand, we are able to able to send messages to the Handler
from any thread, and as a consequence, it is always dispatched to the Looper
thread and handled by the correct Handler
, as shown in the following diagram:

Figure 2.1: Posting work to other Threads
We already saw that we can create our own Looper
threads, but as detailed and mentioned before, the main thread is in also a Looper
thread. To make it more clear, we are going to create a StackTraceHandler
that prints the stack trace of the current thread:
public class StackTraceHandler extends Handler { @Override public void handleMessage(Message msg) { // Prints the Stack Trace on the Android Log Thread.currentThread().dumpStack(); } }
Since the activity's onCreate()
function runs on the main thread, we will create an instance of our handler that implicitly calls the handler's super constructor, which binds the handler to the current thread's Looper
.
Note
If the current thread does not have a Looper and we try to create a handler over the super constructor, a runtime exception with the message Can't create handler inside thread that has not called Looper.prepare() is thrown.
With the Handler
instance created, we retrieve a message from its recycled messages pool by calling the handler's obtainMessage
, and we post an empty message to the main thread's Looper
. The messages obtained by obtainMessage
are cached and will also set the handler as the destination's Handler
object:
public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ... Handler handler = new StackTraceHandler(); Message msg = handler.obtainMessage(); handler.sendMessage(msg); }
As mentioned before, when our handleMessage()
gets dispatched it prints active stack frames at the time of the handleMessage()
execution, as we can see in the following stack trace:
.....StackTraceHandler.handleMessage(StackTraceHandler.java:18)
android.os.Handler.dispatchMessage(Handler.java:99)
android.os.Looper.loop(Looper.java:137)
android.app.ActivityThread.main(ActivityThread.java:4424)
java.lang.reflect.Method.invokeNative(Native Method)
java.lang.reflect.Method.invoke(Method.java:511)
That's right, handleMesage()
is running in the dispatchMessage()
call invoked by the main Looper
, and it is dispatched to the main thread's line of execution.
Sending work to a Looper
Previously, the StackTraceHandler
was implicitly bound to the current main thread's Looper
, so to make it flexible, let's take the next step and make it attachable to any Looper
.
In the following code, we are going to override the default Handler
constructor and define a constructor that accepts the Looper
that is going to the queue, and we will then process and dispatch the message:
public class StackTraceHandler extends Handler { StackTraceHandler(Looper looper){ super(looper); }
Our new constructor basically attaches the Handler to the Looper
passed as an argument, making the StackTraceHandler
attachable to any Looper
instead of the current thread's Looper
.
Our SimpleLooper
was also extended to provide a getter
method to retrieve the Looper
object associated with its thread:
public class SimpleLooper extends Thread{ // start condition boolean started = false; Object startMonitor = new Object(); Looper threadLooper = null; @Override public void run() { Looper.prepare(); threadLooper = Looper.myLooper(); synchronized (startMonitor){ started = true; startMonitor.notifyAll(); } Looper.loop(); } Looper getLooper(){ return threadLooper; } // Threads could wait here for the Looper start void waitforStart(){ synchronized (startMonitor){ while (!started){ try { startMonitor.wait(10); } catch (InterruptedException e) { ... } } } }
Now, from the main thread, we start the SimpleLooper
and its own thread, and when it starts up, we get the Looper
instance to bind our Handler
to the SimpleLooper
thread and Looper
:
SimpleLooper looper = new SimpleLooper(); looper.start(); looper.waitforStart(); Handler handler = new StackTraceHandler(looper.getLooper());
Now, we are going to send the message, as we did in the previous example, from the activity's onCreate()
callback, which runs in the main thread:
Message msg = handler.obtainMessage(); handler.sendMessage(msg);
As we can see in the following stack trace, the thread stack frame at the bottom points to SimpleLooper.run()
, and at the top of the stack trace, we have our Handler
callback, StackTraceHandler.handleMessage
:
at...activity.StackTraceHandler.handleMessage(StackTraceHandler.java:18) at android.os.Handler.dispatchMessage(Handler.java:99) at android.os.Looper.loop(Looper.java:137) at ...activity.SimpleLooper.run(SimpleLooper.java:23)
The interesting thing to realize here is that we can send messages from the main thread to the background thread managed by SimpleLooper
(or even from the background thread to the main thread) and, in doing so, hand over work from background threads to the main thread—for example, to have it update the user interface with the results of background processing.
Scheduling work with post
As we discussed in the previous paragraph, we can submit work to the main or background thread by passing a reference to a Looper
instance into the Handler
constructor.
Exactly what we mean by work can be described by the 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 MessageQueue
belonging to the associated Looper
instance.
We can post work to a Handler
quite easily, for example, by creating an anonymous inner runnable:
final TextView myTextView = (TextView) findViewById(R.id.myTv); // Get the main thread Looper by calling the Context // function getMainLooper Handler handler = new Handler(getMainLooper()); handler.post(new Runnable(){ public void run() { String result = processSomething(); myTextView.setText(result); } });
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() { ... } });
In a single-threaded app, it might seem as if there isn't 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.
Moreover, with the encapsulation of work into more fine-grained units of work, we encourage the reuse of components, improve the testability of the code, and increase the aptitude for work composition:

Figure 2.2: Runnable composition
Using Handler to defer work
When we use the normal post
work function, the work is processed as soon as all the previous units of work are processed on the Looper
—but what happens if we want to schedule some work in 10 seconds' time?
Using Thread.sleep
to block the main thread for 10 seconds would mean that we are holding up the main thread from doing other work, and we are guaranteed to get an ANR dialog. The alternative is to use the handler functions that supply us with deferring functionality:
public class MyRunnable implements Runnable { @Override public void run() { // do some work } }; // Defer work in the main Thread // by 10 seconds time handler.postDelayed(new MyRunnable(), 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):
// Work to be run at a specific time handler.postAtTime(new MyRunnable(), SystemClock. uptimeMillis() + TimeUnit.SECONDS.toMillis(10));
Since postAtTime()
is implemented in terms of an offset from the SystemClock
uptime, the scheduling could suffer from some delay issues, especially if the device has recently fallen in some deep-sleep states. Taking this into account, and when timing accuracy is required, it is usually better to use handler.postDelayed
to defer work.
Leaking implicit references
Deferring work with Handler
and anonymous or nonstatic nested classes requires care in order to avoid potential resource leakage. In these cases, the object submitted to the handler usually creates a reference to the class where it was defined or created. Since the Looper message queue will keep the Runnable
object alive until the scheduled time, an indirect reference to the original Android component could prevent an entire component and its objects from being garbage-collected.
Let's look at this issue with the following examples:
public class MyActivity extends Activity { // non-static inner class public class MyRunnable implements Runnable { @Override public void run() { // do some work } } @Override public void onCreate(Bundle savedInstanceState) { ... // Post Inner class instance Runnable handler.postDelayed(new MyRunnable(), TimeUnit.MINUTES.toMillis(10)); // Post an Anonymous class instance handler.postDelayed(new Runnable() { @Override public void run() { // do some work } }, TimeUnit.MINUTES.toMillis(20)); ... } }
Both objects, the MyRunnable
object created over his default constructor and the anonymous Runnable
class created on the second handler.postDelayed
, hold a reference to the Activity
object.
By declaring an anonymous inner Runnable
inside an activity, we have made an implicit reference to that containing Activity
instance. We've then posted the Runnable
to a handler and told it to execute in 10 minutes' time.
If the activity finishes before the 10 minutes are up, it cannot yet be garbage-collected because the implicit reference in our runnable means that the activity is still reachable by live objects.
So, although it makes for a concise example, it is not a good idea in practice to post non-static Runnables onto the main thread's Handler
queue (especially with postDelayed
or postAtTime
) unless we're very careful to clean up after ourselves all the references to the inactive Activities.
If the MyActivity
object is not garbage-collected in 10 minutes, a memory leak with all the activity views and resources will increase your memory consumption until you reach the maximum heap space per application available. Worse, if you create several instances of this activity when the user navigates through the application, the application will run out of memory in a snap.
Note
The heap size limit available per application varies from device to device. When an application reaches this limit, the system will throw an OutOfMemoryError
.
One way to minimize this problem is to use static nested classes or top-level classes in their own files (direct member of a package) to remove the reference to the original Activity
object when we create a deferred Runnable
work task. This means that references must be explicit, which makes them easier to spot and nullify:
public class MyActivity extends Activity { // static inner class public static class MyRunnable implements Runnable {
Leaking explicit references
If we are to interact with the user interface, we'll at least need a reference to an object in the View
hierarchy, which we might pass into our static or top-level runnable's constructor:
static class MyRunnable implements Runnable { private View view; public MyRunnable(View view) { this.view = view; } public void run() { // ... do something with the view. } }
However, by keeping a strong reference to the View
, we are again subject to potential memory leaks if our Runnable
outlives the View
; for example, if some other part of our code removes this View
from the display before our Runnable
executes.
One solution to this is to use a weak reference and check for null
before using the referenced View
:
static class MyRunnable implements Runnable { private WeakReference<View> view; public MyRunnable(View view) { this.view = new WeakReference<View>(view); } public void run() { View v = view.get(); // might return null if (v != null) { // ... do something with the view. } } }
If you haven't used WeakReference
before, what it gives us is a way to refer to an object only for as long as some other live object has a stronger reference to it (for example, a normal property reference).
When all strong references are garbage-collected, our WeakReference
will also lose its reference to the View
, get()
will return null
, and the View
will be garbage-collected.
This fixes the resource leakage problem, but we must always check for null
before using the returned object in order to avoid potential NullPointerException
instances.
If we're sending messages to our Handler
and expecting it to update the user interface, it will also need a reference to the view hierarchy. A nice way to manage this is to attach and detach the Handler
from onResume
and onPause
:
private static class MyHandler extends Handler { private TextView view; public void attach(TextView view) { this.view = view; } public void detach() { view = null; } @Override public void handleMessage(Message msg) { // handle message } } @Override protected void onResume() { super.onResume(); myHandler.attach(myTextView); } @Override protected void onPause() { super.onPause(); myHandler.detach(); }
Updating the UI with Handler
Since we instantiated our handler in 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:
public void onCreate(Bundle savedInstanceState) { ... // Handler bound to the main Thread final Handler handler = new Handler(); // Creates an assync line of execution Thread thread = new Thread() { public void run() { final String result = searchSynomym("build"); handler.post(new Runnable() { public void run() { TextView text = (TextView) findViewById(R.id.text); text.setText(result); } }); } }; // Start the background thread with a lower priority thread.setPriority(Thread.MIN_PRIORITY); thread.start();
Note
If you start your own threads for background work, make sure to set the priority to Thread.MIN_PRIORITY
to avoid starving the main thread of CPU time. The system CPU scheduler will give more CPU cycle times to threads with higher priority.
Handler
is so fundamental that its API is integrated right into the View
class's member functions:
View.post(Runnable)
.View.postDelayed(action,delayMillis)
.
So, we can rewrite the previous example as follows:
final TextView text = (TextView) findViewById(R.id.text); Thread thread = new Thread(){ public void run(){ final String result = searchSynonym("build"); // Using the view post capabilities text.post(new Runnable(){ public void run() { text.setText(result); } }); } }; thread.setPriority(Thread.MIN_PRIORITY); thread.start();
When writing code in an Activity
class, there is an alternative way to submit a Runnable
on the main thread using the runOnUiThread(Runnable)
method of Activity
, as explained in the previous chapter. If the current thread is the UI thread, then the action is executed immediately. If the current thread is not the UI thread, the action is posted to the event queue of the main UI thread.
Canceling a pending Runnable
During your application execution, you could have a situation where you want to cancel a posted Runnable
, for instance, when you submit a deferred task on your activity's onCreate()
and you want to cancel it when you are executing onDestroy()
because the activity is going to be destroyed. The Handler
function removeCallbacks()
can cancel a pending operation by removing a posted Runnable
task from the queue of work:
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 cancelation applies only to pending tasks—it does not attempt to stop a Runnable
that is already mid-execution.
Note
Keep in mind that if you post the same object more than one time, removeCallbacks()
will remove all the non-running entries that reference that object.
Scheduling work with send
When we post a Runnable
, we can—as seen in the previous examples—define the work at the local or member scope 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 scopes, we could define a static or top-level Runnable
class that we can instantiate from anywhere in our application's lifecycle.
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 the 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) { // Say word } }
Here, we've implemented the handleMessage()
method to expect messages with two different what
values and react accordingly. Apart from the what
property, which is used to identify what the message is about, the message object provides three extra integer fields, arg
, arg2
, and obj
, which can be used to identify and specify your message.
Note
If you look carefully at the Speak
handler class example explained earlier, you'll notice that we defined it as a static class. Subclasses of Handler
should always be declared as top-level or static inner classes to avoid inadvertent memory leaks!
To bind an instance of our Handler
to the main thread, we simply instantiate it from any method that runs on the main thread, such as the Activity onCreate()
callback:
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 here:
handler.sendEmptyMessage(SpeakHandler.SAY_HELLO); ... handler.sendEmptyMessage(SpeakHandler.SAY_BYE);
When we post a message over the previous method, the Handler
will create a message for us, fill in the message's what
property with the integer passed in, and post the message to the handler's Looper
queue. This construct could be extremely useful when we need to send basic commands to a handler, although when we need more complex messages, we need to use other message properties, such as arg1
, arg2
, and obj
, to carry more information about our request.
As messages may carry an object payload as the context for the execution of a 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: // Get an Object 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:
Message msg = Message.obtain(handler, SpeakHandler.SAY_WORD, "Welcome!"); handler.sendMessage(msg);
In the previous example, we basically create a message that the what
property is SAY_WORD
and the obj
property is Welcome!
.
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 each time, 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 that reuses Message
instances from a pool and cuts down on garbage collection overhead. By reducing the memory footprint, fewer objects are recycled, leading to faster and less frequent garbage collection.
Note
In cases where you can build your messages over low-cost integer arguments, you should use them instead of complex arguments such as obj
or data
, which always create extra work for the GC.
Just as we can schedule runnables with 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);
Cancelling pending messages
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
values or by the what
value and object reference:
String myWord = "Do it now!"; handler.removeMessages(SpeakHandler.SAY_BYE); handler.removeMessages(SpeakHandler.SAY_WORD, myWord);
Note that just as with posted runnables, message cancellation only removes pending operations from the queue—it does not attempt to stop an operation already being executed.
Besides the canceling functionality, the handler also provides functions to verify whether there are any pending messages in the queue. With the handler object in hand, we can query the handler by the message's what
value, hasMessages(what)
, and by the hasMethods(what,object)
message object value. Let's put some examples together with our previous examples:
handler.hasMessages(SpeakHandler.SAY_BYE) handler.hasMessages(SpeakHandler.SAY_WORD, myWord)
The first example will verify whether there is any message whose what
code is SAY_BYE
, and the second will verify whether there is any message whose what
code is SAY_WORD
and whose object points to the same reference as myWord
.
It is really important to remember that the removeMessages
and hasMessages
methods with an object argument will search the queue, comparing the object by the ==
reference comparison and not a comparison of object values such as (equals())
. Here is a simple example to explain the situation:
String stringRef1 = new String("Welcome!"); String stringRef2 = new String("Welcome Home!"); Message msg1 = Message.obtain(handler, SpeakHandler.SAY_WORD,stringRef1); Message msg2 = Message.obtain(handler, SpeakHandler.SAY_WORD, stringRef2); // Enqueue the messages to be processed later handler.sendMessageDelayed(msg1,600000); handler.sendMessageDelayed(msg2,600000); // try to remove the messages handler.removeMessages(SpeakHandler.SAY_WORD, stringRef1); handler.removeMessages(SpeakHandler.SAY_WORD, new String("Welcome Home!")); // Create a Print Writer to Process StandardOutput PrintWriterPrinter out = new PrintWriterPrinter(new PrintWriter(System.out,true)); // Dump the Looper State handler.getLooper().dump(out,">> Looper Dump ");
As explained before, the second remove
invocation will not remove the message added previously, because the stringRef1
reference is different from the new reference passed in, despite the string content being the same.
Here is the output from the looper dump, with the message that was not canceled successfully:
>> Looper Dump Looper (main, tid 1) {a15844a} >> Looper Dump Message 0: { when=+10m0s0ms what=2 obj=Welcome Home! target=...SpeakHandler } >> Looper Dump (Total messages: 1, polling=false, quitting=false)
Composition versus inheritance
So far, we've subclassed Handler
to override its handleMessage
method, but that isn't our only option. We can favor composition over inheritance by passing an instance of Handler.Callback
during handler construction:
boolean handleMessage(Message msg)
Let's suppose we want to extend our speaker without changing the original Handler
, and we want to add new actions over a Handler.Callback
class:
public class Speaker implements Handler.Callback { public static final int SAY_WELCOME = 2; public static final int SAY_YES = 3; public static final int SAY_NO = 4; @Override public boolean handleMessage(Message msg) { switch(msg.what) { case SAY_WELCOME: sayWord("welcome"); break; case SAY_YES: sayWord("yes"); break; case SAY_NO: sayWord("no"); 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
value indicating whether or not the Message
was handled. To create a Handler
that uses our extension, we simply pass the Handler.Callback
implementation during handler construction:
Handler handler = new SpeakHandler(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 the default behavior in a Handler
subclass and then mix in special behavior by passing in an instance of Handler.Callback
.
In the aforementioned code, we use the composition to process the SAY_HELLO
message and the inheritance to process the SAY_YES
message:
// will be handled by SpeakHandler Handler handler.sendEmptyMessage(SAY_HELLO); // will be handled by Speaker Handler.Callback handler.sendEmptyMessage(SAY_YES);
Note
Inheritance should only be used when the relationship between a subclass and the superclass is permanent and strong and can't be decoupled. On the other hand, composition offers more flexibility for enhancements and testing.
Multithreading with Handler and ThreadHandler
In a typical Android asynchronous application, the UI thread hands over long computing operations to a background thread, which in turn executes the task and posts back the results to the main thread.
So far, we have just used the Handler
to send messages to the main thread, so the next natural step is to design a multithreaded scenario where the interthread
communication is managed by the Handler
construct.
Let's extend our previous examples and create a weather forecast retriever.
Imagine this scenario: when we click on a UI button, the main thread will ask for our background thread to retrieve the weather forecast and, when the weather forecast response is received, the background thread will ask the main thread to present the weather forecast received.
We will start by creating the WeatherRetriever
that is responsible for receiving the weather forecast requests and then retrieve the forecast sentence and post back the result to the mainHandler
object.
During the scenario assembly, the WeatherRetriever
handler is attached to a background Looper
over the first constructor argument in order to execute in a separate line of execution away from the main thread. The second constructor argument is used to set the handler to post the results.
On the handleMessage
method, the handler is able to process the current day's forecast message requests (GET_TODAY_FORECAST
) or the next day's requests (GET_TOMORROW_FORECAST
), eventually calling the long-computing getForecast()
operation.
The long-computing getForecast()
could block the thread execution for a long time, but this is not a problem anymore, since we are going to run it in a background thread with lower priority, which does not block the UI from being rendered in time, and hence prevents an ANR error from occurring and makes the application more responsive to user interactions:
public class WeatherRetriever extends Handler { private final Handler mainHandler; public static final int GET_TODAY_FORECAST = 1; public WeatherRetriever(Looper looper,Handler mainHandler){ super(looper); this.mainHandler = mainHandler; } // Long Computing Operation String getForecast(){ ... } @Override public void handleMessage(Message msg) { switch(msg.what) { case GET_TODAY_FORECAST: ... final String sentence = getForecast(); Message resultMsg = mainHandler.obtainMessage( WeatherPresenter.TODAY_FORECAST,sentence); this.mainHandler.sendMessage(resultMsg); break; } } };
Secondly, we will build the WeatherPresenter
, which will handle the forecast results coming from the background operation, presenting it to the user on the main thread:
public class WeatherPresenter extends Handler { public static final int TODAY_FORECAST = 1; @Override public void handleMessage(Message msg) { switch(msg.what) { case TODAY_FORECAST: readTodayWeather((String) msg.obj); break; ... } } private void readTodayWeather(String word) { // Present the weather forecast on the UI ... } };
We described one way of setting up a Looper
thread with the SimpleLooper
class, detailed 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("background", Process.THREAD_PRIORITY_BACKGROUND);
Tip
Thread priorities in Android are mapped into Linux nice levels, which govern how often a thread gets to run. A niceness of ?20
is the highest priority, and 19
is the lowest priority. The default nice level is 0
. 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!
In the the next table, the mapping from Android thread priority to Linux nice levels is detailed; however, the use of nice levels lower than -2 in regular applications is not recommended:

HandlerThread
extends java.lang.Thread
, and we must start it with start()
before it actually begins processing its queue:
thread.start();
Now, from an Activity
callback, we are going to detail how to lift up our scenario, building all the instances and objects required to submit and process requests:
// Background Thread private HandlerThread thread; protected void onCreate(Bundle savedInstanceState) { ... WeatherPresenter presHandler = new WeatherPresenter(); // Creates a Thread with a looper attached handlerThread = new HandlerThread("background", Process.THREAD_PRIORITY_BACKGROUND); // start The Thread and waits for work handlerThread.start(); // Creates the Handler to submit requests final WeatherRetriever retHandler = new WeatherRetriever(handlerThread.getLooper(),presHandler);
As we saw previously, the retriever handler is using the HandlerThread's Looper
instead of the main thread's Looper
, so it processes the forecast requests on a background thread, allowing us to run long-computing operations on the WeatherRetriever
.
With a WeatherRetriever
object (retHandler
) reference in our hands, we are able to enqueue a new forecast request to the background thread by sending a message with the WeatherRetriever
handler. In the next example, we listen for taps on the UI's today
button in order to initiate a forecast request:
todayBut.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { retHandler.sendEmptyMessage(WeatherRetriever. GET_TODAY_FORECAST); } }
When the forecast is processed by the background thread on the WeatherRetriever
callback, a message is dispatched to the main Looper
through the WeatherPresenter
reference, and readTodayWeather(String)
is invoked on the main thread in order to present the forecast to the user.
As you can see in the following trace output, the forecast retriever runs on the low-priority background thread with TID 120
, and the forecast result is presented on the main UI thread, which has a TID of 1
:
I/MTHandler(17666): Retrieving Today Forecast at Thread[background,120] I/MTHandler(17666): Presenting Today Forecast at Thread[main,1]
If we create a HandlerThread
to do background work for a specific Activity
, we will want to tie the HandlerThread
instance's life cycle closely to that of the activity's 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:
protected void onPause() { super.onPause(); if (( handlerThread != null) && (isFinishing())) handlerThread.quit(); }
Looper message dispatching debugging
When you want to follow the dispatching and processing of your messages, it could be handy to print a message in the Android logs when any of your messages get routed by the Looper
and when the handler finishes the processing. The Looper
object supplies us with a method to set a printer facility for the message-dispatching debugging, so, from our HandlerThread
, we are able to set it and enable the extra logging required to follow our requests:
... // Creates a Print writer to standard output PrintWriterPrinter out= new PrintWriterPrinter( new PrintWriter(System.out,true) ); handlerThread.getLooper().setMessageLogging(out); ... reqHandler.sendEmptyMessageDelayed ( WeatherRetriever.GET_TODAY_FORECAST, 10000 );
Here is an example of debugging messages printed when our forecast request gets processed by the Looper
:
>>>>> Dispatching to Handler (…WeatherRetriever) {a15844a} null: 1 <<<<< Finished to Handler (...WeatherRetriever) {a15844a} null
The format for the dispatching debug message is (<Target_Handler>) {Callback_Obj} : <what>
.
In our simple example, we print the messages for the process's standard output stream (java.io.OutputStream
), but in more advanced cases, we can print to any kind of OutputStream
subclass (file, network, and so on).
Sending messages versus posting runnables
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 reuses Message
instances, which are sourced from an application-wide pool.
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.
Applications of Handler and HandlerThread
The Handler
class is incredibly versatile, which makes its range of applications very broad.
So far, we've looked at Handler
and HandlerThread
in the context of the Activity
lifecycle, which constrains the sort of applications where this construct might be used—ideally, we do not want to perform long-running operations (more than a second or so) at all in this context.
With that constraint in mind, good candidate uses include performing calculations, string processing, reading and writing small files on the file system, and reading or writing to local databases using a background HandlerThread
.
- Learning Python Web Penetration Testing
- Python概率統計
- SEO智慧
- Effective Python Penetration Testing
- 基于Swift語言的iOS App 商業實戰教程
- 表哥的Access入門:以Excel視角快速學習數據庫開發(第2版)
- C++新經典
- Scala Reactive Programming
- Creating Stunning Dashboards with QlikView
- 用案例學Java Web整合開發
- 編程可以很簡單
- Angular Design Patterns
- WCF技術剖析(卷1)
- MongoDB Cookbook
- Mapping with ArcGIS Pro