summaryrefslogtreecommitdiffstats
path: root/docs/html/resources/articles/timed-ui-updates.jd
diff options
context:
space:
mode:
Diffstat (limited to 'docs/html/resources/articles/timed-ui-updates.jd')
-rw-r--r--docs/html/resources/articles/timed-ui-updates.jd149
1 files changed, 149 insertions, 0 deletions
diff --git a/docs/html/resources/articles/timed-ui-updates.jd b/docs/html/resources/articles/timed-ui-updates.jd
new file mode 100644
index 0000000..863387c
--- /dev/null
+++ b/docs/html/resources/articles/timed-ui-updates.jd
@@ -0,0 +1,149 @@
+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 &lt; 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>
+
+