Advanced Wiring Focus and Threading

As demonstrated in Example 10-9 and "Listening for Touch Events" on page 173, MotionEvents are delivered to the widget whose bounding rectangle contains the coordinates of the touch that generated them. It isn't quite so obvious how to determine which widget should receive a KeyEvent. In order to do this, the Android UI framework, like most other UI frameworks, supports the concept of selection, or focus.

In order to accept focus, a widget's focusable attribute must be set to true. This can be done using either an XML layout attribute (the EditText Views in Example 10-3 have their focusable attribute set to false) or the setFocusable method, as shown in the first line of Example 10-10. A user changes which View has focus using D-pad keys or by tapping the screen when touch is supported.

When a widget is in focus, it usually renders itself with some kind of highlighting to provide feedback that it is the current target of keystrokes. For instance, when an EditText widget is in focus, it is drawn both highlighted and with a cursor at the text insert position.

To receive notification when a widget enters or leaves focus, install an OnFocusChange Listener. Example 10-12 shows the listener needed to add a focus-related feature to the demo program. It causes a randomly positioned black dot to be added automatically to the DotView every now and then, whenever it is in focus.

Example 10-12. Handling focus dotView.setOnFocusChangeListener(new OnFocusChangeListener() {

^Override public void onFocusChange(View v, boolean hasFocus) {

if (!hasFocus && (null != dotGenerator)) { dotGenerator.done(); dotGenerator = null;

else if (hasFocus && (null == dotGenerator)) {

dotGenerator = new DotGenerator(dots, dotView, Color.BLACK); new Thread(dotGenerator).start();

There should be few surprises in the OnFocusChangeListener. When the DotView comes into focus, it creates the DotGenerator and spawns a thread to run it. When the widget leaves focus, the DotGenerator is stopped and freed. The new data member, dotGenerator (whose declaration is not shown in the example), is nonnull only when the DotView is in focus. There is an important and powerful new tool in the implementation of DotGenerator, and we'll return to it in a moment.

Focus is transferred to a particular widget by calling its View method, requestFocus. When requestFocus is called for a new target widget, the request is passed up the tree, parent by parent, to the tree root. The root remembers the widget that is in focus and passes subsequent key events to it directly.

This is exactly how the UI framework changes focus to a new widget in response to a D-pad keystroke. The framework identifies the widget that will be in focus next and calls that widget's requestFocus method. This causes the previously focused widget to lose focus and the target to gain it.

The process of identifying the widget that will gain focus is complicated. In order to do it, the navigation algorithm has to perform some tricky calculations that may depend on the locations of every other widget on the screen.

Consider, for instance, what happens when the right D-pad button is pressed and the framework attempts to transfer focus to the widget immediately to the right of the one that is currently in focus. When looking at the screen, it may be completely obvious which widget that is. In the View tree, however, it is not nearly so obvious. The target widget may be at another level in the tree and several branches away. Identifying it depends on the exact dimensions of widgets in yet other distant parts of the tree. Fortunately, despite the considerable complexity, the Android UI framework implementation usually just works as expected.

When it does not, there are four properties—set either by application method or by XML attribute—that can be used to force the desired focus navigation behavior: nextFocusDown, nextFocusLeft, nextFocusRight, and nextFocusUp. Setting one of these properties with a reference to a specific widget will ensure that D-pad navigation transfers focus directly to that widget when navigating in the respective direction.

Another complexity of the focus mechanism is the distinction that the Android UI framework makes between D-pad focus and touch focus, for devices with touch-sensitive screens. To understand why this is necessary, recall that on a screen that does not accept touch input, the only way to push a button is to focus on it, using D-pad navigation, and then to use the center D-pad key to generate a click. On a screen that does accept touch events, however, there is never any reason to focus on a button. Tapping the button clicks it, regardless of which widget happens to be in focus at the time. Even on a touch-sensitive screen, however, it is still necessary to be able to focus on a widget that accepts keystrokes—an EditText widget, for instance—in order to identify it as the target for subsequent key events. In order to handle both kinds of focus correctly, you will have to look into View's handling of FOCUSABLE_IN_TOUCH_MODE, and the View methods isFocusableInTouchMode and isInTouchMode.

In an application with multiple windows, there is at least one more twist in the focus mechanism: it is possible for a window to lose focus without notifying the currently in-focus widget that its focus has been lost. This makes sense when you think about it. If the out-of-focus window is brought back to the top, the widget that was in focus in that window will again be in focus, with no other action.

Consider entering a friend's phone number into an address book application. Suppose you momentarily switch back to a phone application to refresh your memory of the last few digits of his phone number. You'd be annoyed if, on returning to the address book, you had to focus again on the EditText box in which you'd been typing. You expect the state to be just as you left it.

On the other hand, this behavior can have surprising side effects. In particular, the implementation of the auto-dot feature presented in Example 10-12 continues to add dots to the DotView even when it is hidden by another window. If a background task should run only when a particular widget is visible, that task must be cleaned up when the widget loses focus, when the Window loses focus, and when the Activity is paused or stopped.

Most of the implementation of the focus mechanism is in the ViewGroup class, in methods such as requestFocus and requestChildFocus. Should it be necessary to implement an entirely new focus mechanism, you'll need to look carefully at these methods, and override them appropriately.

Leaving the subject of focus and returning to the implementation of the newly added auto-dot feature, Example 10-13 contains the implementation of DotGenerator.

Example 10-13. Enqueuing a task for the main thread private final class DotGenerator implements Runnable { final Dots dots; final DotView view; final int color;

private final Handler hdlr = new Handler(); private final Runnable makeDots = new Runnable() { public void run() { makeDot(dots, view, color); }

private volatile boolean done;

// Runs on the main thread

DotGenerator(Dots dots, DotView view, int color) { this.dots = dots; this.view = view; this.color = color;

// Runs on the main thread public void done() { done = true; }

// Runs on a different thread! public void run() { while (!done) {

try { Thread.sleep(lOOO); } catch (InterruptedException e) { } hdlr.post(makeDots);Q

Here are some of the highlights of the code:

O Creates an android.os.Handler object, the new tool introduced in this section.

© Creates a new anonymous Runnable object. It is used to call MakeDot from the main thread in item 4.

© The Constructor for DotGenerator. The DotGenerator is created on the main thread.

O The dot generation loop generates a new dot about every second. This is the only part of the code that runs on a completely different thread.

A naive implementation of DotGenerator would simply call makeDot directly within its run method. Doing this wouldn't be safe, however, unless makeDot was thread-safe— and the Dots and DotView classes were too, for that matter. This would be tricky to get correct and hard to maintain. In fact, the Android UI framework actually forbids access to a View from multiple threads. Running the naive implementation would cause the application to fail with an Android runtime error like this:

11-30 02:42:37.471: ERROR/AndroidRuntime(l62): android.view.ViewRoot$CalledFromWrongThreadException:

Only the original thread that created a view hierarchy can touch its views.

To get around this restriction, DotGenerator creates a Handler object within its constructor. A Handler object is associated with the thread on which it is created and provides safe, concurrent access to a canonical event queue for that thread.

Because DotGenerator creates a Handler during its own construction (which happens on the main thread), this Handler is associated with the main thread. Now DotGenerator can use the Handler to enqueue a Runnable that calls makeDot from the main thread. As you might guess, it turns out that the main-thread event queue on which the Handler enqueues the Runnable is exactly the same one that is used by the UI

framework. The call to makeDot is dequeued and dispatched, like any other UI event, in its proper order. In this case, that causes its Runnable to be run. makeDot is called from the main thread and the UI stays single-threaded.

This is a very important pattern for coding with the Android UI framework. Whenever processing started on behalf of the user might take more than a few milliseconds to complete, doing that processing on the main thread might cause the entire UI to become sluggish or, worse, to freeze for a long time. If the main application thread does not service its event queue for a couple of seconds, the Android OS will kill the application for being unresponsive. The Handler class allows the programmer to avoid this danger by delegating slow or long-running tasks to other threads, so that the main thread can continue to service the UI. When a task completes, it uses a main-thread Handler to enqueue an update for the UI.

The demo application takes a slight shortcut here: it enqueues the creation of a new dot and its addition to the dot model on the main thread. A more complex application might pass a main thread Handler to the Model on creation, and provide a way for the UI to get a model-thread Handler from the model. The main thread would receive update events enqueued for it by the Model, using its main-thread Handler. The Model, running in its own thread, would use the Looper class to dequeue and dispatch incoming messages from the UI.

Passing events between the UI and long-running threads in this way dramatically reduces the constraints required to maintain thread safety. In particular, note that if an enqueuing thread retains no references to an enqueued object, or if that object is immutable, no additional synchronization is necessary.

0 0

Post a comment