Previous section   Next section

Mobile Phone Handheld Hardware Hardware Rick Rogers John Lombardo O'Reilly Media, Inc. O'Reilly Media Android Application Development, 1st Edition

10.3. Wiring Up the Controller

Section 10.2 demonstrated a view with two buttons. Although the buttons look nice—they even highlight when clicked—they aren't very useful. Clicking them doesn't actually do anything.

The discussion of Section 10.1.3 described how the Android framework translates external actions (screen taps, key presses, etc.) into events that are enqueued and then passed into the application. Example 10-4 shows how to add an event handler to one of the buttons in the demo, so that it does something when it is clicked.

Example 10-4. Wiring up a button

@Override public void onCreate(Bundle state) {
    super.onCreate(state);
    setContentView(R.layout.main);

    final EditText tb1 = (EditText) findViewById(R.id.text1);
    final EditText tb2 = (EditText) findViewById(R.id.text2);

    ((Button) findViewById(R.id.button2)).setOnClickListener(
        new Button.OnClickListener() {
            @Override public void onClick(View arg0) {
                tb1.setText(String.valueOf(rand.nextInt(200)));
                tb2.setText(String.valueOf(rand.nextInt(200)));
            }
        }
    );
}

When run, this version of the application still looks a lot like Figure 10-2. Unlike the earlier example, though, in this version every time a user clicks the button labeled "Green", the numbers in the EditText boxes change. This is illustrated in Figure 10-4.

Figure 10-4. Working button


Simply changing numbers isn't very interesting, but this small example demonstrates the standard mechanism that an application uses to respond to UI events. It is important to note that, appearances notwithstanding, this example does not violate the MVC separation of concerns. In response to the call to setText, in this implementation of an OnClickListener, the EditText object updates an internal representation of the text it should display, and then calls its own invalidate method. It does not immediately draw on the screen. There are very few rules in programming that are absolute, but the admonition to separate the Model, the View, and the Controller comes pretty close.

In the example, the instance of the Button class is wired to its behavior using its setOnClickListener method. Button is a subclass of View, which defines an interface named OnClickListener and a method named setOnClickListener, which registers the listener. The OnClickListener interface defines a single method, onClick. When a Button receives an event from the framework, in addition to any other processing it might do, it examines the event to see whether it qualifies as a "click." (The button in Example 10-1 would highlight when pressed, even before the listener was added.) If the event does qualify as a click and if a click listener has been installed, that listener's onClick method is invoked.

The click listener is free to implement any custom behavior that's needed. In the example, the custom behavior creates two random numbers between 0 and 200 and puts one into each of the two text boxes. Instead of subclassing Button and overriding its event processing methods, all that is necessary to extend its behavior is to register a click listener that implements the behavior. Certainly a lot easier!

The click handler is especially interesting because at the heart of the Android system—the framework event queue—there is no such thing as a click event. Instead, View event processing synthesizes the concept of a "click" from other events. If the device has a touch-sensitive screen, for instance, a single tap is considered a click. If the device has a center key in its D-pad or an "Enter" key, pressing and releasing either will also register as a click. View clients need not concern themselves with what a click is or how it is generated on a particular device. They need only handle the higher-level concept, leaving the details to the framework.

A View can have only one OnClickListener. Calling setOnClickListener a second time on a given View will remove the old listener and install the new one. On the other hand, a single listener can listen to more than one View. The code in Example 10-5, for instance, is part of another application that looks exactly like Example 10-2. In this version, though, pushing either of the buttons will update the text box.

This capability can be very convenient in an application in which several actions produce the same behavior. Do not be tempted, though, to create a single enormous listener to handle all your widgets. Your code will be easier to maintain and modify if it contains multiple smaller listeners, each of which implements a single, clear behavior.

Example 10-5. Listening to multiple buttons

@Override public void onCreate(Bundle state) {
    super.onCreate(state);
    setContentView(R.layout.main);

    final EditText tb1 = (EditText) findViewById(R.id.text1);
    final EditText tb2 = (EditText) findViewById(R.id.text2);

    Button.OnClickListener listener = new Button.OnClickListener() {
        @Override public void onClick(View arg0) {
            tb1.setText(String.valueOf(rand.nextInt(200)));
            tb2.setText(String.valueOf(rand.nextInt(200)));
        } };

    ((Button) findViewById(R.id.button1)).setOnClickListener(listener);
    ((Button) findViewById(R.id.button2)).setOnClickListener(listener);
}

10.3.1. Listening to the Model

The Android UI framework uses the handler installation pattern pervasively. Although our earlier examples were all Buttons, many other Android widgets define listeners. The View class defines several events and listeners that are ubiquitous, and which we will explore in further detail later in this chapter. Other classes, however, define other specialized types of events and provide handlers for those events that are meaningful only for those classes. This is a standard idiom that allows clients to customize the behavior of a widget without having to subclass it.

This pattern (called the Callback Pattern) is also an excellent way for your program to handle its own external, asynchronous actions. Whether responding to a change in state on a remote server or an update from a location-based service, your application can define its own events and listeners to allow its clients to react.

The examples so far have been elementary and have cut several corners. Although they demonstrate connecting a View and a Controller, they have not had real Models. (Example 10-4 actually used a String owned by the implementation of EditText as a Model.) In order to proceed, we're going to have to take a brief detour to build a real, usable Model.

The two classes in Example 10-6 comprise a Model that will support extensions to the demo application for this chapter. They provide a facility for storing a list of objects, each of which has X and Y coordinates, a color, and a size. They also provide a way to register a listener, and an interface that the listener must implement.

Example 10-6. The Dots Model

package com.oreilly.android.intro.model;


/** A dot: the coordinates, color and size. */
public final class Dot {
    private final float x, y;
    private final int color;
    private final int diameter;

    /**
     * @param x horizontal coordinate.
     * @param y vertical coordinate.
     * @param color the color.
     * @param diameter dot diameter.
     */
    public Dot(float x, float y, int color, int diameter) {
        this.x = x;
        this.y = y;
        this.color = color;
        this.diameter = diameter;
    }

    /** @return the horizontal coordinate. */
    public float getX() { return x; }

    /** @return the vertical coordinate. */
    public float getY() { return y; }

    /** @return the color. */
    public int getColor() { return color; }

    /** @return the dot diameter. */
    public int getDiameter() { return diameter; }
}



package com.oreilly.android.intro.model;

import java.util.Collections;
import java.util.LinkedList;
import java.util.List;


/** A list of dots. */
public class Dots {
    /** DotChangeListener. */
    public interface DotsChangeListener {
        /** @param dots the dots that changed. */
        void onDotsChange(Dots dots);
    }

    private final LinkedList<Dot> dots = new LinkedList<Dot>();
    private final List<Dot> safeDots = Collections.unmodifiableList(dots);
    
    private DotsChangeListener dotsChangeListener;
    
    /** @param l the new change listener. */
    public void setDotsChangeListener(DotsChangeListener l) {
        dotsChangeListener = l;
    }

    /** @return the most recently added dot, or null. */
    public Dot getLastDot() {
        return (dots.size() <= 0) ? null : dots.getLast();
    }
    
    /** @return the list of dots. */
    public List<Dot> getDots() { return safeDots; }

    /**
     * @param x dot horizontal coordinate.
     * @param y dot vertical coordinate.
     * @param color dot color.
     * @param diameter dot size.
      */
    public void addDot(float x, float y, int color, int diameter) {
        dots.add(new Dot(x, y, color, diameter));
        notifyListener();
    }

    /** Delete all the dots. */
    public void clearDots() {
        dots.clear();
        notifyListener();
    }

    private void notifyListener() {
        if (null != dotsChangeListener) {
            dotsChangeListener.onDotsChange(this); 
        }
    }
}

In addition to using this model, the next example also introduces a widget used to view it, the DotView: it will be discussed later, in Example 12-3. For now we introduce it as a library widget. Its job is to draw the dots represented in the Model, in the correct color and at the correct coordinates. The complete source for the application is on the website for this book.

Example 10-7 shows the new demo application, after adding the new Model and View.

Example 10-7. Dots demo

package com.oreilly.android.intro;

import java.util.Random;

import android.app.Activity;
import android.graphics.Color;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
10 import android.widget.EditText;
import android.widget.LinearLayout;

import com.oreilly.android.intro.model.Dot;
import com.oreilly.android.intro.model.Dots;
import com.oreilly.android.intro.view.DotView;


/** Android UI demo program */
public class TouchMe extends Activity {
    public static final int DOT_DIAMETER = 6;

    private final Random rand = new Random();

    final Dots dotModel = new Dots();
    
    DotView dotView;

    /** Called when the activity is first created. */
    @Override public void onCreate(Bundle state) {
        super.onCreate(state);

        dotView = new DotView(this, dotModel);
        
        // install the view
        setContentView(R.layout.main);
        ((LinearLayout) findViewById(R.id.root)).addView(dotView, 0);
        
        // wire up the controller
        ((Button) findViewById(R.id.button1)).setOnClickListener(
            new Button.OnClickListener() {
                @Override public void onClick(View v) {
                    makeDot(dots, dotView, Color.RED);
                } });
        ((Button) findViewById(R.id.button2)).setOnClickListener(<-- (1)
            new Button.OnClickListener() {<-- (2)
                @Override public void onClick(View v) {<-- (3)
                    makeDot(dots, dotView, Color.GREEN);
                } });

        final EditText tb1 = (EditText) findViewById(R.id.text1);
        final EditText tb2 = (EditText) findViewById(R.id.text2);
        dots.setDotsChangeListener(new Dots.DotsChangeListener() {
            @Override public void onDotsChange(Dots d) {
                Dot d = dots.getLastDot();
                tb1.setText((null == d) ? "" : String.valueOf(d.getX()));
                tb2.setText((null == d) ? "" : String.valueOf(d.getY()));
                dotView.invalidate();
            } });
    }
  

    /**
     * @param dots the dots we're drawing
     * @param view the view in which we're drawing dots
     * @param color the color of the dot
     */
    void makeDot(Dots dots, DotView view, int color) {
        int pad = (DOT_DIAMETER + 2) * 2;
        dots.addDot(
            DOT_DIAMETER + (rand.nextFloat() * (view.getWidth() - pad)),
            DOT_DIAMETER + (rand.nextFloat() * (view.getHeight() - pad)),
            color,
            DOT_DIAMETER);
    }
}

Here are some of the highlights of the code:

Figure 10-5 shows what the application looks like when run.

Figure 10-5. Running the Dots demo


Pushing the button labeled "Red" adds a new red dot to the DotView. Pushing the "Green" button adds a green one. The text fields contain the coordinates of the last dot added.

The basic structure of Example 10-2 is still recognizable, with some extensions. For example, here is the chain of events that results from clicking the "Green" button:

  1. When the button is clicked, its clickHandler is called.

  2. This causes a call to the anonymous class installed as an OnClickHandler. It, in turn, calls makeDot with the color argument Color.GREEN. The makeDot method generates random coordinates and adds a new green Dot to the Model at those coordinates.

  3. When the Model is updated, it calls its DotsChangedListener.

  4. The listener updates the values in the text views and requests that the DotView be redrawn.

10.3.2. Listening for Touch Events

Modifying the demo application to handle taps is just a matter of adding a tap handler. The code in Example 10-8 extends the demo application to place a cyan dot in the DotView at the point at which the screen is tapped. In the previous example, the code would be added at the beginning of the onCreate function right after the call to its parent method. Notice that, because the code that displays the X and Y coordinates of the most recently added dot is wired only to the Model, it continues to work correctly, no matter how dots are added.

Example 10-8. Touchable Dots

dotView.setOnTouchListener(new View.OnTouchListener() {
    @Override public boolean onTouch(View v, MotionEvent event) {
        if (MotionEvent.ACTION_DOWN != event.getAction()) {
            return false;
        }
        dots.addDot(event.getX(), event.getY(), Color.CYAN, DOT_DIAMETER);
        return true;
    } });

The MotionEvent passed to the handler has several other properties in addition to the location of the tap that caused it. As the example indicates, it also contains the event type, one of DOWN, UP, MOVE, or CANCEL. A simple tap actually generates one DOWN and one UP event. Touching and dragging generates a DOWN event, a series of MOVE events, and a final UP event.

The facilities provided by the MotionEvent for handling gestures are very interesting. The event contains the size of the touched area and the amount of pressure applied. That means that, on devices that support it, an application might be able to distinguish between a tap with one finger and a tap with two fingers, or between a very light brush and a firm push.

Efficiency is still important in the mobile world. A UI framework confronts the horns of a dilemma when tracking and reporting touchscreen events. Reporting too few events might make it impossible to follow motion with sufficient accuracy to do, for instance, handwriting recognition. On the other hand, reporting too many touch samples, each in its own event, could load the system unacceptably. The Android UI framework addresses this problem by bundling groups of samples together, reducing the load and still maintaining accuracy. To see all of the samples associated with an event, use the history facility implemented with the methods getHistoricalX, getHistoricalY, etc.

Example 10-9 shows how to use the history facility. It extends the demo program to track a user's gestures when she touches the screen. The framework delivers sampled X and Y coordinates to the onTouch method of an object installed as the OnTouchListener for the DotView. The method displays a cyan dot for each sample.

Example 10-9. Tracking motion

private static final class TrackingTouchListener
    implements View.OnTouchListener
{
    private final Dots mDots;

    TrackingTouchListener(Dots dots) { mDots = dots; }

    @Override public boolean onTouch(View v, MotionEvent evt) {
        switch (evt.getAction()) {
              case MotionEvent.ACTION_DOWN:
                break;

            case MotionEvent.ACTION_MOVE:
                for (int i = 0, n = evt.getHistorySize(); i < n; i++) {
                    addDot(
                        mDots,
                        evt.getHistoricalX(i),
                        evt.getHistoricalY(i),
                        evt.getHistoricalPressure(i),
                        evt.getHistoricalSize(i));
                }
                break;

            default:
                return false;
        }

        addDot(
            mDots,
            evt.getX(),
            evt.getY(),
            evt.getPressure(),
            evt.getSize());

        return true;
    }

    private void addDot(Dots dots, float x, float y, float p, float s) {
        dots.addDot(
            x,
            y,
            Color.CYAN,
            (int) ((p * s * Dot.DIAMETER) + 1));
    }
}

Here are some highlights of the code:

Figure 10-6 shows what the extended version of the application might look like after a few clicks and drags.

The implementation uses the size and pressure at a given location's sample to determine the diameter of the dot drawn there. Unfortunately, the Android emulator does not emulate touch pressure and size, so all of the dots have the same diameter. Size and pressure values are normalized across devices, as floating-point values between 0.0 and 1.0, depending on the calibration of the screen. It is possible, however, that either value may actually be larger than 1.0. At the other end of the range, the emulator always reports the event size as zero.

Figure 10-6. Running the Dots demo after adding the touch tracking feature


Devices with trackballs also generate MotionEvents when the trackball is moved. These events are similar to those generated by taps on a touch-sensitive screen, but they are handled differently. Trackball MotionEvents are passed into the View tHRough a call to dispatchTrackballEvent, not to dispatchTouchEvent, which delivered taps. Although dispatchTrackballEvent does pass the event to onTrackballEvent, it does not first pass the event to a listener! Not only are trackball-generated MotionEvents not visible through the normal tap-handling machinery, but, in order to respond to them, a widget must subclass View and override the onTrackballEvent method.

MotionEvents generated by the trackball are handled differently in yet another way. If they are not consumed (to be defined shortly) they are converted into D-pad key events (like those that would be generated by left, right, up and down arrow keys). This makes sense when you consider that most devices have either a D-pad or a trackball, but not both. Without this conversion, it wouldn't be possible to generate D-pad events on a device with only a trackball. Of course, it also implies that an application that handles trackball events must do so carefully, lest it break the translation.

10.3.3. Listening for Key Events

Handling keystroke input across multiple platforms can be very tricky. Some devices have many more keys than others, some require triple-tapping for character input, and so on. This is a great example of something that should be left to the framework (EditText or one of its subclasses) whenever possible.

To extend a widget's KeyEvent handling, use the View method setOnKeyListener to install an OnKeyListener. The listener will be called with multiple KeyEvents for each user keystroke, one for each action type: DOWN, UP, and MULTIPLE. The action types DOWN and UP indicate a key was pressed or released, just as they did for the MotionEvent class. A key action of MULTIPLE indicates that a key is being held down (autorepeating). The KeyEvent method geTRepeatCount gives the number of keystrokes that a MULTIPLE event represents.

Example 10-10 shows a sample key handler. When added to the demo program, it causes dots to be added to the display at randomly chosen coordinates when keys are pressed and released: a magenta dot when the Space key is pressed and released, a yellow dot when the Enter key is pressed and released, and a blue dot when any other key is pressed and released.

Example 10-10. Handling keys

dotView.setFocusable(true);

dotView.setOnKeyListener(new OnKeyListener() {
    @Override public boolean onKey(View v, int keyCode, KeyEvent event) {
        if (KeyEvent.ACTION_UP != event.getAction()) {
            int color = Color.BLUE;
            switch (keyCode) {
                case KeyEvent.KEYCODE_SPACE:
                    color = Color.MAGENTA;
                    break;
                case KeyEvent.KEYCODE_ENTER:
                    color = Color.YELLOW;
                    break;
                default: ;
            }

            makeDot(dots, dotView, color);
        }

        return true;
    } });

10.3.4. Alternative Ways to Handle Events

You've probably noticed that the on... methods of all of the listeners introduced thus far—including onKey—return a boolean value. This is a pattern for listeners that allows them to control subsequent event processing by their caller.

When a Controller event is handed to a widget, the framework code in the widget dispatches it to an appropriate method, depending on its type: onKeyDown, onTouchEvent, etc. These methods, either in View or one its subclasses, implement the widget's behavior. As described earlier, though, the framework first offers the event to an appropriate listener (OnTouchListener, OnKeyListener, etc.) if one exists. The listener's return value determines whether the event is then dispatched to the View methods.

If the listener returns false, the event is dispatched to the View methods as if the handler did not exist. If, on the other hand, a listener returns true, the event is said to have been consumed. The View aborts any further processing for it. The View methods are never called and have no opportunity to process or respond to the event. As far as the View methods are concerned, it is as if the event did not exist.

There are three ways that an event might be processed:


No listener

The event is dispatched to the View methods for normal handling. A widget implementation may, of course, override these methods.


A listener exists and returns true

Listener event handling completely replaces normal widget event handling. The event is never dispatched to the View.


A listener exists and returns false

The event is processed by the listener and then by the View. After listener event handling is completed, the event is dispatched to the View for normal handling.

Consider, for instance, what would happen if the key listener from Example 10-10 were added to an EditText widget. Since the onKey method always returns true, the framework will abort any further KeyEvent processing as soon as the method returns. That would prevent the EditText key-handling mechanism from ever seeing the key events, and no text would ever appear in the text box. That is probably not the intended behavior!

If the onKey method instead returns false for some key events, the framework will dispatch those events to the EditText widget for continued processing. The EditText mechanism will see the events, and the associated characters will be appended to the EditText box, as expected. Example 10-11 shows an extension of Example 10-10 that, besides adding new dots to the Model, also filters the characters passed to the hypothetical EditText box. It allows numeric characters to be processed normally but hides everything else.

Example 10-11. Handling keys

new OnKeyListener() {
    @Override public boolean onKey(View v, int keyCode, KeyEvent event) {
        if (KeyEvent.ACTION_UP != event.getAction()) {
            int color = Color.BLUE;
            switch (keyCode) {
                case KeyEvent.KEYCODE_SPACE:
                    color = Color.MAGENTA;
                    break;
                case KeyEvent.KEYCODE_ENTER:
                    color = Color.YELLOW;
                    break;
                default: ;
            }

            makeDot(dotModel, dotView, color);
        }

        return (keyCode < KeyEvent.KEYCODE_0)
            ||(keyCode > KeyEvent.KEYCODE_9);
    }
}

If your application needs to implement entirely new ways of handling events (in other words, if it is something that cannot be implemented reasonably by augmenting behavior and filtering, using an OnKeyHandler), you will have to understand and override View key event handling. To summarize the process, events are dispatched to the View tHRough the DispatchKeyEvent method. DispatchKeyEvent implements the behavior described previously, offering the event to the onKeyHandler first. If the handler returns false, it offers the event to the View methods implementing the KeyEvent.Callback interface: onKeyDown, onKeyUp, and onKeyMultiple.

10.3.5. Advanced Wiring: Focus and Threading

As demonstrated in Example 10-9 and Section 10.3.2, Motio⁠nEvents 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 OnFocusChangeLis⁠tener. 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(1000); }
            catch (InterruptedException e) { }
            hdlr.post(makeDots);
        }
    }
}

Here are some of the highlights of the code:

A naïve 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(162):
 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 DotGe⁠nera⁠tor 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.

          
      Previous section   Next section
     


    Honeywell hcm890 humidifier problem solving, honeywell humidifier reviews in Arlington.