Building responsive apps with Handler

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.

Scheduling work with post

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.

Tip

As we'll see in the Issues section, posting an anonymous Runnable makes for concise examples but when used with postDelayed or postAtTime requires care to avoid potential resource leakage.

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();

Tip

If you start your own threads for the background work, make sure to set the priority to Thread.MIN_PRIORITY to avoid starving the main thread of CPU time.

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.

Canceling a pending Runnable

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.

Scheduling work with send

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.

Tip

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 classes to avoid inadvertent memory leaks!

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

Composition versus Inheritance

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.

Multithreaded example

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.

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

Building responsive apps with HandlerThread

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);

Tip

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();
}
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.15.25.186