150 lines
6.5 KiB
Plaintext
150 lines
6.5 KiB
Plaintext
|
page.title=Updating the UI from a Timer
|
||
|
@jd:body
|
||
|
|
||
|
<img style="margin: 1.5em; float: right;" src="images/JFlubber.png" alt="" id="BLOGGER_PHOTO_ID_5135098660116808706" border="0">
|
||
|
|
||
|
<p><strong>Background</strong>: While developing my first useful
|
||
|
(though small) application for Android, which was a port of an existing
|
||
|
utility I use when podcasting, I needed a way of updating a clock
|
||
|
displayed on the UI at regular intervals, but in a lightweight and CPU
|
||
|
efficient way.</p>
|
||
|
|
||
|
<p><strong>Problem</strong>: In the original application I used
|
||
|
java.util.Timer to update the clock, but that class is not such a good
|
||
|
choice on Android. Using a Timer introduces a new thread into the
|
||
|
application for a relatively minor reason. Thinking in terms of mobile
|
||
|
applications often means re-considering choices that you might make
|
||
|
differently for a desktop application with relatively richer resources
|
||
|
at its disposal. We would like to find a more efficient way of updating
|
||
|
that clock.</p>
|
||
|
|
||
|
<p><strong>The Application</strong>: The original application is a
|
||
|
Java Swing and SE application. It is like a stopwatch with a lap timer
|
||
|
that we use when recording podcasts; when you start the recording, you
|
||
|
start the stopwatch. Then for every mistake that someone makes, you hit
|
||
|
the flub button. At the end you can save out the bookmarked mistakes
|
||
|
which can be loaded into the wonderful
|
||
|
<a href="http://audacity.sourceforge.net/" title="Audacity">Audacity</a>
|
||
|
audio editor as a labels track. You can then see where all of the mistakes
|
||
|
are in the recording and edit them out.</p>
|
||
|
|
||
|
<p>The article describing it is: <a href="http://www.developer.com/java/ent/print.php/3589961" title="http://www.developer.com/java/ent/print.php/3589961">http://www.developer.com/java/ent/print.php/3589961</a></p>
|
||
|
|
||
|
<p>In the original version, the timer code looked like this:</p>
|
||
|
|
||
|
<pre>class UpdateTimeTask extends TimerTask {
|
||
|
public void run() {
|
||
|
long millis = System.currentTimeMillis() - startTime;
|
||
|
int seconds = (int) (millis / 1000);
|
||
|
int minutes = seconds / 60;
|
||
|
seconds = seconds % 60;
|
||
|
|
||
|
timeLabel.setText(String.format("%d:%02d", minutes, seconds));
|
||
|
}
|
||
|
}</pre><p>And in the event listener to start this update, the following Timer() instance is used:
|
||
|
</p><pre>if(startTime == 0L) {
|
||
|
startTime = evt.getWhen();
|
||
|
timer = new Timer();
|
||
|
timer.schedule(new UpdateTimeTask(), 100, 200);
|
||
|
}</pre>
|
||
|
|
||
|
<p>In particular, note the 100, 200 parameters. The first parameter
|
||
|
means wait 100 ms before running the clock update task the first time.
|
||
|
The second means repeat every 200ms after that, until stopped. 200 ms
|
||
|
should not be too noticeable if the second resolution happens to fall
|
||
|
close to or on the update. If the resolution was a second, you could
|
||
|
find the clock sometimes not updating for close to 2 seconds, or
|
||
|
possibly skipping a second in the counting, it would look odd).</p>
|
||
|
|
||
|
<p>When I ported the application to use the Android SDKs, this code
|
||
|
actually compiled in Eclipse, but failed with a runtime error because
|
||
|
the Timer() class was not available at runtime (fortunately, this was
|
||
|
easy to figure out from the error messages). On a related note, the
|
||
|
String.format method was also not available, so the eventual solution
|
||
|
uses a quick hack to format the seconds nicely as you will see.</p>
|
||
|
|
||
|
<p>Fortunately, the role of Timer can be replaced by the
|
||
|
android.os.Handler class, with a few tweaks. To set it up from an event
|
||
|
listener:</p>
|
||
|
|
||
|
<pre>private Handler mHandler = new Handler();
|
||
|
|
||
|
...
|
||
|
|
||
|
OnClickListener mStartListener = new OnClickListener() {
|
||
|
public void onClick(View v) {
|
||
|
if (mStartTime == 0L) {
|
||
|
mStartTime = System.currentTimeMillis();
|
||
|
mHandler.removeCallbacks(mUpdateTimeTask);
|
||
|
mHandler.postDelayed(mUpdateTimeTask, 100);
|
||
|
}
|
||
|
}
|
||
|
};</pre>
|
||
|
|
||
|
<p>A couple of things to take note of here. First, the event doesn't
|
||
|
have a .getWhen() method on it, which we handily used to set the start
|
||
|
time for the timer. Instead, we grab the System.currentTimeMillis().
|
||
|
Also, the Handler.postDelayed() method only takes one time parameter,
|
||
|
it doesn't have a "repeating" field. In this case we are saying to the
|
||
|
Handler "call mUpdateTimeTask() after 100ms", a sort of fire and forget
|
||
|
one time shot. We also remove any existing callbacks to the handler
|
||
|
before adding the new handler, to make absolutely sure we don't get
|
||
|
more callback events than we want.</p>
|
||
|
|
||
|
<p>But we want it to repeat, until we tell it to stop. To do this, just
|
||
|
put another postDelayed at the tail of the mUpdateTimeTask run()
|
||
|
method. Note also that Handler requires an implementation of Runnable,
|
||
|
so we change mUpdateTimeTask to implement that rather than extending
|
||
|
TimerTask. The new clock updater, with all these changes, looks like
|
||
|
this:</p>
|
||
|
|
||
|
<pre>private Runnable mUpdateTimeTask = new Runnable() {
|
||
|
public void run() {
|
||
|
final long start = mStartTime;
|
||
|
long millis = SystemClock.uptimeMillis() - start;
|
||
|
int seconds = (int) (millis / 1000);
|
||
|
int minutes = seconds / 60;
|
||
|
seconds = seconds % 60;
|
||
|
|
||
|
if (seconds < 10) {
|
||
|
mTimeLabel.setText("" + minutes + ":0" + seconds);
|
||
|
} else {
|
||
|
mTimeLabel.setText("" + minutes + ":" + seconds);
|
||
|
}
|
||
|
|
||
|
mHandler.postAtTime(this,
|
||
|
start + (((minutes * 60) + seconds + 1) * 1000));
|
||
|
}
|
||
|
};</pre>
|
||
|
|
||
|
<p>and can be defined as a class member field.</p>
|
||
|
|
||
|
<p>The if statement is just a way to make sure the label is set to
|
||
|
10:06 instead of 10:6 when the seconds modulo 60 are less than 10
|
||
|
(hopefully String.format() will eventually be available). At the end of
|
||
|
the clock update, the task sets up another call to itself from the
|
||
|
Handler, but instead of a hand-wavy 200ms before the update, we can
|
||
|
schedule it to happen at a particular wall-clock time — the line: start
|
||
|
+ (((minutes * 60) + seconds + 1) * 1000) does this.</p>
|
||
|
|
||
|
<p>All we need now is a way to stop the timer when the stop button
|
||
|
is pressed. Another button listener defined like this:</p>
|
||
|
|
||
|
<pre>OnClickListener mStopListener = new OnClickListener() {
|
||
|
public void onClick(View v) {
|
||
|
mHandler.removeCallbacks(mUpdateTimeTask);
|
||
|
}
|
||
|
};</pre>
|
||
|
|
||
|
<p>will make sure that the next callback is removed when the stop button
|
||
|
is pressed, thus interrupting the tail iteration.</p>
|
||
|
|
||
|
<p>Handler is actually a better choice than Timer for another reason
|
||
|
too. The Handler runs the update code as a part of your main thread,
|
||
|
avoiding the overhead of a second thread and also making for easy
|
||
|
access to the View hierarchy used for the user interface. Just remember
|
||
|
to keep such tasks small and light to avoid slowing down the user
|
||
|
experience.</p>
|
||
|
|
||
|
|