Mobile Phone Handheld Hardware Hardware Rick Rogers John Lombardo O'Reilly Media, Inc. O'Reilly Media Android Application Development, 1st Edition10.3. Wiring Up the ControllerSection 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. 
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 ModelThe 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 Modelpackage 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 demopackage 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. 
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: When the button is clicked, its clickHandler is called. 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. When the Model is updated, it calls its DotsChangedListener. The listener updates the values in the text views and requests
that the DotView be
redrawn.
10.3.2. Listening for Touch EventsModifying 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 DotsdotView.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 motionprivate 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. 
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 EventsHandling 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 keysdotView.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 EventsYou'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 keysnew 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 ThreadingAs demonstrated in Example 10-9 and Section 10.3.2, 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 OnFocusChangeListener.
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 focusdotView.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 threadprivate 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 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.
 |