5. Looper/Handler

The ants go marching one by one. Hurrah! Hurrah!

Children’s song

Although AsyncTasks are the first concurrency construct that most developers encounter when they start working with Android, they are not its central concurrency construct. The basic concurrency construct in Android is the Looper/Handler.

Introducing the Looper/Handler

The Looper/Handler framework, like several other important parts of Android’s internal architecture, was probably inspired by the BeOS operating system—in particular, its BLooper and BHandler classes.

The Looper framework is an extension of the safe publication idiom, introduced Chapter 2, “Java Concurrency,” and shown again in Figure 5.1. The idiom consists of a drop box and a lock on that drop box.

Image

Figure 5.1 Safe publication

The essential point of the idiom is that an object that is not itself thread-safe can be published safely from one thread into another, by passing it through a correctly synchronized intermediate. The transfer is safe, as long as the sender retains no references to the passed object—and, of course, the passed object retains no references to objects owned by the source thread.

For simplicity, the example in Chapter 2 used a single-item drop box to hold the passed object while in transit. The Android Looper is an extension of the safe publication idiom that uses a sorted queue for object publication, instead of the simple drop box.

The Looper framework is illustrated in Figure 5.2. The Worker Thread is the thread into which the object will be published. The MessageQueue takes the place of the drop box. Clients seize the lock on the MessageQueue and enqueue an object to be passed. The Worker Thread seizes the same lock and dequeues the object. The only difference from the Chapter 2 idiom is that instead of holding a single item, the MessageQueue holds many, and establishes the order in which they are published.

Image

Figure 5.2 The Looper framework

The Handler is an added feature in the Looper/Handler framework. It abstracts away some the details of the underlying thread and MessageQueue.

The Looper framework consists of four Java classes and some native C code. The Java classes are as follows:

Image android.os.Looper

Image android.os.Handler

Image android.os.MessageQueue

Image android.os.Message

There is one additional helper class:

Image android.os.HandlerThread

Basic Looper/Handler

The Looper is more than a simple publication mechanism. Its goal is to transfer a small amount of work, a task, from a source thread to the Looper’s worker thread, for execution. It is simply an event queue powered by a single Java worker thread. When initialized with a Looper, a thread acquires two special features:

Image A reference to a single instance of MessageQueue.

Image The queue manager, the Looper’s loop method, that is called from the worker thread’s run method.

When a Java thread is initialized as a Looper, its run method calls Looper.loop. The loop method is a nonterminating loop that removes tasks from the Looper’s MessageQueue and calls back to the Handler that enqueued them, from the Looper’s worker thread, to get them processed.

Walking through the execution of a single task will illustrate the process. Suppose that some thread—called the source thread here—has a task that it would like to have executed by another thread, the worker thread, which has been initialized with a Looper. Figure 5.3 demonstrates.

Image

Figure 5.3 Using a Handler to pass a message to a Looper

The source thread begins (Figure 5.3, pane 1) by creating a new Handler for the target Looper:

Handler handler = new Handler(looper);

The variable looper is a reference to the worker thread’s Looper. After the assignment, the variable handler contains a reference to a Handler object, which, in turn, holds a reference to the worker thread’s MessageQueue.

A single Handler can be used to enqueue many tasks. Once the code running on the source thread has a reference to a Handler, it can use that Handler to create, enqueue, and process as many tasks as it likes.

Once the source thread has a reference to a Handler for the target worker thread, it uses that Handler to obtain a Message object from a pool. It attaches the task that it would like executed on the worker thread to the Message and then uses the Handler to enqueue the Message for the Looper (see Figure 5.3, pane 2):

Message msg = handler.obtainMessage(DELIVER_STUFF, stuff);
handler.sendMessage(msg);

The Handler uses correct synchronization to enqueue the Message, with its reference to the task, on the Looper’s MessageQueue (Figure 5.3, pane 3). Of course, all the rules from the preceding chapters apply. If the passed task object is not itself thread-safe, the source thread must delete all references to it and to anything to which it refers. Similarly, the task must not hold any back references to objects in use by the source thread.

All the code, thus far, has run on the source thread. Once the Message is enqueued on the Looper’s MessageQueue, though, its ownership is transferred to the Looper’s worker thread. Eventually the Message is dequeued from the MessageQueue in the Looper’s loop method on the Looper’s worker thread. The Looper hands the dequeued message back to the Handler that originally enqueued it—still on the worker thread—for processing (Figure 5.3, pane 4). The Handler is responsible for processing the Message and its associated task in a callback method on the worker thread.

Delegating Execution

The Looper/Handler framework provides two idioms for task processing. Although a single underlying mechanism supports both idioms, they are completely distinct. When trying to understand the framework, it helps to think of them as entirely different tools that, because of environmental constraints, have been crammed into a single implementation.

Posting a Runnable

The simplest way of delegating execution to another thread using the Looper/Handler framework is a collection of methods on Handler whose names begin with post .... These methods take as an argument a Runnable, which becomes the payload that is transferred to the worker thread. When one of the Handler post ... methods is invoked, the Handler, internally, obtains a Message object, attaches the Runnable to it, and enqueues the Message on the MessageQueue for the Looper with which the Handler is associated.

As illustrated in Figure 5.3, when a Message is removed from the MessageQueue by the target Looper, it is passed back to the Handler that originally enqueued it, for processing. The Handler internally recognizes the Message’s attached Runnable, extracts it, and calls its run method. Listing 5.1 demonstrates posting a Runnable that starts an animation after a short delay.

Listing 5.1 Naïve Delayed Animation


private uiHandler;

@Override
@SuppressLint("HandlerLeak")
public void onCreate(Bundle state) {
    super.onCreate(state);

    // ...

    uiHandler = new Handler();
}

// ...

private void revealButtonDelayed() {
    uiHandler.postDelayed(
        new Runnable() {
            @Override
            public void run() {
                button.setVisibility(View.VISIBLE);
                ViewAnimationUtils
                    .createCircularReveal(button, ctr, ctr, 0, buttonDiameter)
                    .start();
            } },
        BUTTON_DELAY);
}


When a Handler is created without explicitly specifying a target Looper as an argument to the Handler’s constructor, the Handler, by default, uses the Looper for the thread on which the constructor is run. If the current thread has not been initialized as a Looper, attempting to create a Handler without explicitly specifying a target Looper will cause a runtime exception.

In Listing 5.1, the Handler to which the variable uiHandler refers targets the Looper associated with the main thread. This is guaranteed because the onCreate method always runs on the main thread. It works because Android’s main thread, it turns out, is the worker thread for a Looper. The static method Looper.getMainLooper is another way to get the same Looper, the Looper for the main thread, and it can be used from nearly any environment.

The code in the listing submits a Runnable that will start an animation that reveals button after BUTTON_DELAY milliseconds. There is a serious flaw, and one that should by now be familiar, in the code in Listing 5.1. Consider what happens if the fragment is executed just before the user rotates her device.

All the concerns that applied to AsyncTasks apply here. The immediate source of failure will be an exception thrown by the animation framework when it attempts to animate a button that is no longer part of the active view hierarchy. Remember that when the device is rotated, the entire view hierarchy is discarded and replaced with a new one.

In addition, recall that because the Runnable in Listing 5.1 is an anonymous class, it holds a reference to the surrounding Activity. The Activity cannot be garbage collected until the Runnable is freed, BUTTON_DELAY milliseconds hence.

These problems can be addressed in a number of ways. Choosing among them requires a clear understanding of the specifics. One could naively apply a broad, generic concurrency pattern. In this particular case, however, there is a much more lightweight solution. It is illustrated in Listing 5.2.

Listing 5.2 Improved Delayed Animation


Runnable animator;
private uiHandler;

@Override
@SuppressLint("HandlerLeak")
public void onCreate(Bundle state) {
    super.onCreate(state);

    // ...

    handler = new Handler();
}

@Override
public void onPause() {
    if (null != animator) {
        handler.removeCallbacks(animator);
        animator = null;
    }
    super.onPause();
}

// ...

private void showButton() {
    if (animator != null) { return; }

    Runnable anim = new Runnable() {
        @Override
        public void run() {
            button.setVisibility(View.VISIBLE);
            ViewAnimationUtils
                .createCircularReveal(button, ctr, ctr, 0, buttonDiameter)
                .start();
            animator = null;
        }
    };

    uiHandler.postDelayed(anim, BUTTON_HIDE_DELAY);
    animator = anim;
}


The important insight is that all the code in the example is running on a single thread, the main thread. There is no need for complex synchronization or locking. In addition, because a single thread executes all the code, each method is atomic with respect to the others. The thread that executes the run method of the animation Runnable must return from that method before it can execute any other method. In particular, this implies that the execution of the methods run and onPause are mutually exclusive.

Consider the possible states for Listing 5.2. In the initial state, the variable animator is null and a call to onPause will simply call super.onPause. A call to showButton in the initial state will execute the entire method, enqueueing the delayed animation and setting animator non-null. The only way that animator can be set non-null is when an animation is queued.

There are two ways of returning to the initial state. The first way is through the normal completion of the animator. The main thread dequeues the Message containing the animation Runnable and executes it, starting the animation. Once it does that, no animation task is still queued and animator is reset to null, the initial state. Figure 5.4 illustrates a typical execution of the two methods.

Image

Figure 5.4 Typical execution

The second way of returning to the initial state is that onPause is called while an animation task is in the message queue. In this case, animator is not null so onPause removes the message to which it refers from the message queue, and sets animator to null—the initial state. The only way that animator can be set null is when a message is dequeued. In short, animator is non-null only during the time that an animation Runnable is in the queue.

Note also that when the Message is dequeued in onPause, there are no longer any dangling references to the Runnable. Because of that, the Runnable is eligible for garbage collection and will not prevent the Activity, to which it has an implicit pointer, from being garbage collected.

All the code in Listing 5.2 must run on the main thread because it manipulates view objects. Android view objects must never be touched from any other thread. Because they run on the same thread, the two methods—the Runnable’s run and the Activity’s onPause—are run asynchronously, but not concurrently.

It is interesting to consider how different the solution would have to be were this not the case—if run and onPause could run concurrently. At a minimum, the variable animator would have to be volatile because it would be accessed from more than one thread. More troublesome, though, is that if the Activity’s onPause and the animation Runnable’s run methods could run on different threads, they would not be atomic with respect to one another; there would be no happens-before relationship between them.

In such a scenario, there is an opportunity for a race in the interval between the time the Runnable is removed from the MessageQueue and the time its run method completes. The onPause method is supposed to cancel all animations. If it happens to execute during this interval it could not, without additional synchronization, guarantee that the run method hadn’t scheduled a new animation, after it completed. Figure 5.5 illustrates this race condition: the worker thread dequeues a message and starts an animation, even after onPause completes.

Image

Figure 5.5 Race condition

This hypothetical situation—onPause and run executing on different threads—is only a little more difficult. It is definitely not insurmountable. Another handful of lines of code would fix it. The point, though, is that because run and onPause must run on the same thread, that handful of lines is the very best kind of code: code you don’t need. The use of a Looper/Handler in Listing 5.2 does not imply massive synchronization and a profusion of concurrency constructs. Instead, after clear-eyed evaluation of the actual situation, it turns out that a very simple solution works just fine.

Enqueueing a Message

Although posting Runnables is very convenient, there are important problems with using them as a way of delegating execution.

The first problem is architectural. An application in which any component, anywhere, can post a Runnable to any Handler can quickly become a maintenance nightmare. Code enqueued to execute in some other place at some other time can be extremely hard to debug.

Also, because it is the central concurrency mechanism in Android, nearly every event of any kind that affects an application is represented, at some point in its lifecycle, as a Message enqueued for processing by a Looper. During some animations, for instance, there are events that occur every 17ms or so. Add to this all the events originating from the network, such as the keyboard, geo-location sensors, and whatever other source might show up. Together, they amount to hundreds of events every second.

That’s a lot of events. If each event required creating a new Runnable object and then garbage collecting that object when it was no longer needed, the garbage collector would be very busy indeed. As a very sensible optimization, the Looper/Handler framework supports a pool of recyclable Message objects. Instead of transporting a Runnable, the Message object can itself identify a task to be performed and supply a few parameters for it.

There are several ways to get a Message from the pool. Probably the most convenient is to get it from the Handler that will enqueue and process it. A collection of overloaded methods on Handler named obtainMessage... retrieve and initialize the Message from the pool. For example:

Handler handler = new Handler(looper);
handler.obtainMessage(OP_SHOW_BUTTON).sendToTarget();

The call to obtainMessage returns a Message with its what field initialized to OP_SHOW_BUTTON (a Java int) and its target field—the field that contains a reference to the Handler to which the Message will be passed for processing—initialized with a reference to handler. The call to sendToTarget causes the Message to be handed back to handler to be enqueued on Looper’s message queue.


Note: Method Overloading

An overloaded method is one that has multiple signatures but a single name. For instance, the Handler methods obtainMessage(), obtainMessage(int), obtainMessage(int, int, int), obtainMessage(int, int, int, Object), and obtainMessage(int, Object) are collectively an example of an overloaded method, obtainMessage.


When using this idiom, the behavior associated with a message is in the Handler that processes that message and not, as it was in the Runnable idiom, in the message itself. Listing 5.3 is an implementation of the feature originally implemented in Listing 5.2 using only Messages. This idiom and its switch statement are extremely common in Android code.

Listing 5.3 Messaged Delayed Animation


private static final int OP_START_ANIMATION = -1;

private boolean animating;
private Handler handler;

@Override
@SuppressLint("HandlerLeak")
public void onCreate(Bundle state) {
    super.onCreate(state);

    // ...

    handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case OP_START_ANIMATION:
                    animateButton();
                    break;
                default:
                    super.handleMessage(msg);
            }
        }
    };
}

@Override
public void onPause() {
    if (animating) {
        handler.removeMessages(OP_START_ANIMATION);
        animating = false;
    }
    super.onPause();
}

// ...

private void showButton() {
    if (animating) { return; }
    handler.sendMessageDelayed(
        handler.obtainMessage(OP_START_ANIMATION),
        BUTTON_HIDE_DELAY);
    animating = true;
}

void animateButton() {
    button.setVisibility(View.VISIBLE);
    ViewAnimationUtils
        .createCircularReveal(button, ctr, ctr, 0, buttonDiameter)
        .start();
    animating = false;
}


The analysis of this implementation is nearly identical to that of the code in Listing 5.2. The only change is that, instead of passing an object that will actually be executed, showButton now enqueues only a recyclable Message, containing an opcode in its what field. The case statement in the Handler switches on opcode values to call the appropriate method: code that would, in the Runnable idiom, have been the contents of the Runnable’s run method. After handleMessage completes, the Handler automatically returns the Message to the message pool.

Although the recyclable message idiom is much more lightweight, it is also more limited. A Runnable can have nearly any conceivable behavior and, like a closure, can inherit state from the surrounding environment. A Message is only data. It can only invoke behaviors already in the Handler case statement and can only transfer data that is specifically attached to it. These limitations, however, can make code clearer and more thread-safe. Although the choice is up to the developer, the lightweight idiom is preferable in all except the simplest cases.

It is important to reiterate that the Handler used in all three of the preceding examples has the potential to cause memory leaks. Because it is an inner class, it has an implicit pointer to the Activity that contains it. Messages enqueued using the Handler hold a pointer to that Handler. Once a message is enqueued, the MessageQueue, in turn, holds a reference to the Message. The MessageQueue is a very long-lived object referred to by the thread that powers the Looper. Until the last message from a given Handler has been removed from the queue, neither the Handler nor the Activity to which it refers is eligible for garbage collection.

In the examples in this chapter, this is not significant because onPause removes all the Messages that refer to the Handler from the queue before the Activity is eligible for destruction. If the next developer who modifies this code is not as careful, though, it would be all too easy to leak the Activity. To prevent this possibility, Android Lint recommends that all Handlers be static inner class and hold at most a WeakReference to their Activity.

This is the heart of the Looper/Handler framework: a single thread servicing a single queue on which any number of other threads can enqueue Messages using Handlers. The Handler gives very fine-grained control over how the messages are processed, being both the producer that is invoked on the local thread to enqueue Messages for eventual processing, and the consumer that runs on a Looper’s worker thread and processes those Messages.

Some Gory Details

The previous sections provide most of the information necessary to use the Looper/Handler framework. There are some details, however, that can help you to use it more effectively.

Handlers and Messages

A Message is a small, simple object: nearly a POJO (Plain Old Java Object). Most of its fields are actually managed by the Handler.

Messages themselves are not thread-safe. None of the Message object’s fields are final or volatile. An enqueueing thread must not retain a reference to a Message that has been enqueued for a different thread.

Figure 5.6 is a schematic diagram of a Message.

Image

Figure 5.6 The Message

As the figure indicates, there are four groups of fields with related purposes:

Image Admin group: used by the framework for internal management

Image Runnable: Runnable execution

Image Switched: Switched execution

Image Remote: Inter-process communication

The Admin Fields

The framework uses the administrative fields for basic message processing. All the fields are private to the framework (package-protected). Of most interest are when, the time at which the message is to be processed, and target, mentioned previously, the reference to the Handler to which the message will be given for processing.

The when field implements a timed queue. As demonstrated in the previous examples, messages can be enqueued for execution at specific future times. The Looper/Handler message queue is sorted by the when field and the Looper will not dequeue and process a message until the current time, as measured by SystemClock.uptimeMillis, is greater than or equal to the time stored in the Message’s when field. Several overloaded Handler methods support timed delays:

Image postAtTime(runnable, ..., uptimeMillis)

Image postDelayed(runnable, delayMillis)

Image sendMessageAtTime(message, uptimeMillis)

Image sendMessageDelay(message, delayMillis)

Although the unadorned post... and send... methods simply set when to be the current time, these methods enable clients of the framework to submit messages for execution either at specific times or at some delta from the current time. When using the absolute time version of these post methods, be sure to use SystemClock.uptimeMillis and not System.currentTimeMillis!

The target field is a reference to the Handler to which a message will be given for processing. There are several ways to set the target. Interestingly, all but one of them are irrelevant.

Calling one of Handler’s overloaded obtain... methods returns a Message whose target field is preset to refer to the called Handler. Similarly, all the static obtain... methods on Message take a Handler as the first argument. In fact, the Handler methods simply call the Message methods, passing this as their first argument. There is even a third possibility, a setTarget method on Message instances.

None of these ways of setting the Message target is significant in any way. When a Message is enqueued for processing, the Handler that enqueues it sets its target, as part of the enqueueing process, to refer to itself. The Handler that enqueues the Message is always its target. Listing 5.4 is an example of code that is seriously confusing but enqueues a Message with what set to OP_CONFUSION, on looper1’s MessageQueue, for processing by handler1.

Listing 5.4 Target Confusion


Handler handler1 = new Handler(looper1);
Handler handler2 = new Handler(looper2);

handler1.sendMessage(handler2.obtainMessage(OP_CONFUSION));


Although the Message is obtained from handler2, enqueueing it with handler1 sets the target to be handler1.


Note

Message.setTarget()? Srsly!?

One wonders about the wisdom of exposing this method. The possibilities for misuse completely dwarf any possibility for benefit—if there even is one!

Consider:

Message msg = Message.obtain();

handler.sendMessage();

msg.setTarget(null); //!!!


The remaining admin field, flag, is used primarily for the FLAG_IN_USE bit. This bit is used to track the lifecycle of a Message. It is set when a Message is released into the message pool, cleared when it is removed from the pool, and checked when it is enqueued or released to the pool. If it is set at either of the two checks, an exception is thrown. This guarantees that Messages are never doubly freed back into the pool, and that they are never enqueued once they’ve been released. Despite its name, the bit is set only when the Message is in the pool and not in use. The other flag, FLAG_ASYNCHRONOUS affects scheduling and will be discussed shortly.

The Messaging Fields

The two non-overlapping sets of fields classified here as “Runnable” and “Switched” represent the two separate means of processing a Message, explored in the examples in Listings 5.2 and 5.3. If callback is non-null, the Handler completely ignores Switched fields and invokes the run method of the Runnable to which callback refers. This behavior can be changed. It is implemented in the Handler method dispatchMessage, which is not final and can be overridden.

If callback is null, dispatchMessage interprets the message as Switched. Switched messages are processed in one of two ways. If the Handler instance was initialized with an instance of Handler.Callback, that Callback’s handleMessage method is called, passing the message. If the Handler instance does not have a Handler.Callback, or if the call to Handler.Callback.handleMessage returns false the Handler’s own handleMessage method is called with the message. Handler, the base class, does implement handleMessage, but its implementation is empty: It will simply ignore all messages. A subclass of Handler that does not either pass a Handler.Callback or override handleMessages is useful only for posting Runnables.

Switched messages are typically processed with a switch on the Message what field. The Android source itself, AOSP, is full of this kind of use of the Looper/Handler framework.

The fields arg1 and arg2 are simple, int values, available for use as arguments to the code selected for execution by the what field. The third argument, obj, is a reference to an Object and thus enables the inclusion of arbitrary additional parameters. It also implies all the usual thread-safety constraints.

The Remote Fields

The remaining fields in the Message are used for inter-process communication. They will be discussed in Chapter 6, “Services, Processes, and IPC.”

Starting a Looper

A Looper is, as mentioned at the beginning of this chapter, simply a queue manager, initialized with a single Java thread and a MessageQueue. Whereas there can be multiple Handlers associated with a single Looper, there can be only a single Looper associated with a given thread. Unlike Java’s Executor framework, the Looper framework supports only a single thread servicing a single queue.

Initializing a thread as a Looper is a four-step process:

1. Create and start the thread.

2. From the running thread, call the static method Looper.prepare.

3. Perform any additional initialization.

4. From the running thread, call the static method Looper.loop. This method will not return until the Looper is stopped.

Listing 5.5 demonstrates a simple Looper.

Listing 5.5 Looper Creation


Thread looperThread = new Thread() {
    @Override
    public void run() {
        Looper.prepare();
        Looper.loop();
    }
};
looperThread.start();


Although looperThread is now running as a Looper, it is not very useful. The Looper/Handler framework does not provide any way to find the Looper that is associated with an arbitrary thread if one exists. Because there is no way to find the Looper associated with looperThread—even though there is one—there is no way to associate a Handler with it and therefore no way to submit or process work using it.

The Android documentation suggests creating a Handler as part of the initialization of the Looper thread, as shown in Listing 5.6.

Listing 5.6 Looper Creation


public volatile Handler mainHandler;

// ...

    Thread looperThread = new Thread() {
        @Override
        public void run() {
            Looper.prepare();
            mainHandler = new Handler();
            Looper.loop();
        }
    };
    looperThread.start();


There is a variant of the idiom shown in Listing 5.6. In this alternative, the publication of the reference to the Handler into the variable mainHandler is replaced by the publication of a reference to the Looper, using the method Looper.myLooper. The myLooper method returns the Looper with which the current thread has been initialized, and can be used at any time after the call to Looper.prepare.

Although the two variants are nearly equivalent, the code in Listing 5.6 might be clearer than the alternative when the Looper has a single specific purpose. A canonical Handler instantiated just before entering the loop can explicitly implement that purpose, thus simply and clearly expressing the function of the code.

Publishing a reference to a single Handler is sufficient, even if it will be necessary to add additional Handlers later. A call to the Handler method getLooper returns a reference to a Handler’s Looper, for use in initializing other Handlers.

The code in Listing 5.6 contains a significant data race. As written, there is no simple way to tell when the assignment to mainHandler takes place. The Thread method start schedules the new thread and returns immediately. It makes no guarantees about when the newly scheduled thread will actually begin execution. The assignment to mainHandler might be visible as soon as start returns or only after the execution of hundreds of statements.

The Android convenience class HandlerThread avoids this race and makes creating a new Looper a completely straightforward process. Listing 5.7 demonstrates this.

Listing 5.7 Looper Creation


HandlerThread looperThread = new HandlerThread("AlternateLooper");
looperThread.start();
Looper looper = looperThread.getLooper(); // may block!


The HandlerThread internally looks a lot like Listing 5.6. It adds an additional synchronization mechanism in the getLooper method that blocks until the HandlerThread begins execution and initializes its Looper. When the method returns, eventually, looper is guaranteed to be non-null. Obviously, because it blocks, HandlerThread.getLooper should be called from code that is not extremely time-sensitive—perhaps at component initialization.


Note

Don’t forget to start your threads!

The code in Listing 5.7 will block permanently, unless there is a call to looperThread’s start method! Simply creating the thread does not schedule it for execution.


The Native Looper

Every Looper has a native shadow that is a near a mirror image of the Java framework but is written in C/C++. The native shadow has a native analog for each Looper/Handler framework Java component: Message, MessageQueue, Handler and the Looper. Perhaps surprisingly, the native Looper is scheduled on the same thread as the Java Looper. A Looper’s worker thread, therefore, actually services two separate queues: the Java queue and the native queue. The native queue is polled before the Java queue.

It is the native Looper that surrenders the CPU when there are no messages ready for execution. When there are no schedulable messages in either queue, the native Looper yields the processor using the Linux epoll mechanism. epoll is a kernel primitive that enables a thread to wait for new data to appear in any of a set of file descriptors. Because it yields the processor in this way, a Looper can be awakened asynchronously by another thread that writes data to one of the file descriptors on which the Looper is waiting.

The epoll mechanism also supports timed-wait. This mechanism enables a thread to wait until either new data arrives or a specified amount of time elapses, whichever happens first. A Looper uses the epoll timed-wait mechanism to go to sleep, scheduling itself to be restarted at the time at which the next Message in its MessageQueue is scheduled for processing.

This is safe, even if the wake-up time is a considerable distance in the future. If a Message is enqueued by some other thread and must be scheduled before the sleeping Looper thread is scheduled to wake up, writing a byte to a canonical pipe (not surprisingly, called the mWakeWritePipeFd) will wake it up.

Nearly the only way to enqueue tasks on a Looper’s native queue is to use the native framework. Because of this, most Loopers, with the obvious exception of main thread, never had any significant work in their native queue. Polling the native queue is only minor overhead.

As of Android 6.0, Marshmallow, the Looper/Handler framework exposes several new methods in Java. Among these is MessageQueue.isIdle, a method that queries the queue for scheduled tasks. It can be used to determine directly whether a Looper is idle or not.

There are also two new methods that provide direct Java access to the epoll mechanism: MessageQueue.addOnFileDescriptorEventListener and MessageQueue.removeOnFileDescriptorEventListener. These two methods enable Java code to register new file descriptors and callbacks, in the native Looper’s epoll file descriptor wait set.

Scheduling and the Sync-Barrier

A discussion of details would not be complete without some consideration of the specifics of task scheduling. Task scheduling is implemented in the MessageQueue method next.

Perhaps the first thing to notice is that, because the MessageQueue’s queue is a simple linked list sorted by execution time, the computational complexity of enqueueing a task is O(n). Because n is normally very small, the overhead is probably negligible in most cases. The normal case of scheduling a task without specifying the time at which it is to run, however, almost certainly will cause a scan of the entire queue. On a heavily used Looper, like that associated with the main thread, this overhead can be significant.

As described earlier, a Looper yields the processor by invoking native code. Just before the queue-polling code on the Java side of the framework passes control to the native half with the intention of yielding the CPU, it invokes any registered MessageQueue.IdleHandlers. This provides a mechanism through which code can release resources that are needed only while tasks are being processed. A network interface, for instance, might open a network connection as part of processing the first request delivered from the queue. It might then register a MessageQueue.IdleHandler to close that connection after the last request is completed.

The most interesting twist to task scheduling, however, is something called a sync-barrier. Sync-barriers appeared in Android in the JellyBean release. Sync-barriers are used to stall a Looper so that a concurrent task can be executed synchronously. In particular, Android 5.0, Lollypop, API level 21 and Material Design introduced a new UI architecture, optimized for animation and high-performance graphics. Among other changes, this architecture makes heavy use of a new, dedicated rendering thread separate from the main thread. The framework needs a way to suspend the execution of UI-related main thread tasks, while the render thread traverses the view tree to render the view.

Sync-barriers introduce a new dimension for tasks. In addition to the post.../send... distinction—Message/Runnable processing—since JellyBean, messages are also either synchronous or asynchronous. Asynchronous messages are simply messages that can be processed despite a sync-barrier. Android API level 22 provides the method Message.setAsynchronous that gives developers the capability to control this attribute of a task.

A sync-barrier is implemented as a Message with a null target. There is no way to create such a message, correctly, with the visible interface. It is possible only with the method MessageQueue.postSyncBarrier. As of Android version 6.0, this method is hidden with @hide and called only from code that traverses the view hierarchy. The synchronous/asynchronous distinction is relevant only on the main thread.

When the MessageQueue task scheduling code encounters a target-less message, it continues to scan the task queue but ignores any messages that have not been marked as asynchronous (their FLAG_ASYNCHRONOUS flag is set). If there are asynchronous messages ready for execution, they are scheduled normally. If no asynchronous message is ready for execution, the Looper yields the processor and schedules wake-up for the execution time of the first asynchronous task in the queue. If there are no asynchronous messages in the queue, the sleep time is -1, indefinite.

The only way to un-stall a blocked MessageQueue is to remove the sync-barrier explicitly. The hidden MessageQueue method removeSyncBarrier removes the barrier, wakes up the thread, and corrects the wake-up time to be the time of the next schedulable task.

The use of sync-barriers can have a surprising effect on the accuracy of scheduling on the main thread. Table 5.1 shows standard deviations for execution times for three different types of tasks. The test code used to create these measurements schedules trivial Messages for delivery at 10ms in the future and, upon execution, calculates the difference between the scheduled and actual execution times. The tests represented in the table were performed while running a simple, full-screen animation, and they were run on several different devices. Although specific standard deviation varied from device to device, the numbers shown in the table are characteristic.

Image

Table 5.1 Execution Time Standard Deviation (Typical Device)

A task scheduled on a freshly created Looper running only the test tasks had a standard deviation of less than .2 ms. The same test run on the main thread using a Message marked as asynchronous produced a slightly higher standard deviation. This is to be expected; the main thread, running an animation, is already busy.

The big effect comes when the task is run on the main thread and is not marked asynchronous. Under these circumstances, the standard deviation goes up by an order of magnitude, to a very significant 2 ms.

Summary

The Looper is the basic concurrency structure in Android. It supports scheduled, asynchronous task execution in a safe and lightweight framework.

The Looper/Handler framework is quite complex. It has two halves, one Java and one native. The native half is based on the Linux epoll mechanism and supports waiting for a specified length of time, or for new data to be written into one of a set of file descriptors.

The framework supports the execution of several types of messages. It supports both the direct execution of a Java Runnable and a more frugal mode in which the code to be executed—usually in a case statement in the Handler method handleMessage—is selected using an opcode in the Message’s what field.

The Android framework initializes the main thread, which is created as part of starting a new process for a new application with a Looper. The main thread is just the canonical Looper’s worker thread, the thread on which UI components (and nearly everything else, except by special arrangement) are run. The main thread’s Looper is always available via the Looper method getMainLooper.

A careful reader, after contemplating the Looper/Handler framework, could re-implement AsyncTasks in terms of Looper/Handlers. In fact, there is some evidence that this is exactly how the original AsyncTask was implemented. As noted previously, there are hints in the documentation—though none in readily accessible code—that AsyncTasks were, in the earliest versions of Android, run on a single thread. That thread was very likely a Looper.

Because Loopers are powered by a single thread, enqueueing a task that runs for thirty minutes will stall every other job in the queue for thirty minutes. The dreaded ANR, Application Not Responding error, results, exactly from stalling the main thread and preventing it from processing tasks in its MessageQueue in a timely way.

The semantics of the Looper/Handler framework—in-order execution on a single thread—are, as discussed in the preceding chapter, fairly appealing. They do mean, however, that a developer must be aware of the profiles of the tasks being enqueued. On the main thread, for instance, flipping the flag on a view object, computing the dimensions and location of a view object, and even painting the background of a large view object are all things that can be done reasonably. Network calls, database queries, and resizing an image are all things that cannot.

..................Content has been hidden....................

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