1page.title=Updating the UI from a Timer 2parent.title=Articles 3parent.link=../browser.html?tag=article 4@jd:body 5 6<img style="margin: 1.5em; float: right;" src="images/JFlubber.png" alt="" id="BLOGGER_PHOTO_ID_5135098660116808706" border="0"> 7 8<p><strong>Background</strong>: While developing my first useful 9(though small) application for Android, which was a port of an existing 10utility I use when podcasting, I needed a way of updating a clock 11displayed on the UI at regular intervals, but in a lightweight and CPU 12efficient way.</p> 13 14<p><strong>Problem</strong>: In the original application I used 15java.util.Timer to update the clock, but that class is not such a good 16choice on Android. Using a Timer introduces a new thread into the 17application for a relatively minor reason. Thinking in terms of mobile 18applications often means re-considering choices that you might make 19differently for a desktop application with relatively richer resources 20at its disposal. We would like to find a more efficient way of updating 21that clock.</p> 22 23<p><strong>The Application</strong>: The original application is a 24Java Swing and SE application. It is like a stopwatch with a lap timer 25that we use when recording podcasts; when you start the recording, you 26start the stopwatch. Then for every mistake that someone makes, you hit 27the flub button. At the end you can save out the bookmarked mistakes 28which can be loaded into the wonderful 29<a href="http://audacity.sourceforge.net/" title="Audacity">Audacity</a> 30audio editor as a labels track. You can then see where all of the mistakes 31are in the recording and edit them out.</p> 32 33<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> 34 35<p>In the original version, the timer code looked like this:</p> 36 37<pre>class UpdateTimeTask extends TimerTask { 38 public void run() { 39 long millis = System.currentTimeMillis() - startTime; 40 int seconds = (int) (millis / 1000); 41 int minutes = seconds / 60; 42 seconds = seconds % 60; 43 44 timeLabel.setText(String.format("%d:%02d", minutes, seconds)); 45 } 46}</pre><p>And in the event listener to start this update, the following Timer() instance is used: 47</p><pre>if(startTime == 0L) { 48 startTime = evt.getWhen(); 49 timer = new Timer(); 50 timer.schedule(new UpdateTimeTask(), 100, 200); 51}</pre> 52 53<p>In particular, note the 100, 200 parameters. The first parameter 54means wait 100 ms before running the clock update task the first time. 55The second means repeat every 200ms after that, until stopped. 200 ms 56should not be too noticeable if the second resolution happens to fall 57close to or on the update. If the resolution was a second, you could 58find the clock sometimes not updating for close to 2 seconds, or 59possibly skipping a second in the counting, it would look odd).</p> 60 61<p>When I ported the application to use the Android SDKs, this code 62actually compiled in Eclipse, but failed with a runtime error because 63the Timer() class was not available at runtime (fortunately, this was 64easy to figure out from the error messages). On a related note, the 65String.format method was also not available, so the eventual solution 66uses a quick hack to format the seconds nicely as you will see.</p> 67 68<p>Fortunately, the role of Timer can be replaced by the 69android.os.Handler class, with a few tweaks. To set it up from an event 70listener:</p> 71 72<pre>private Handler mHandler = new Handler(); 73 74... 75 76OnClickListener mStartListener = new OnClickListener() { 77 public void onClick(View v) { 78 if (mStartTime == 0L) { 79 mStartTime = System.currentTimeMillis(); 80 mHandler.removeCallbacks(mUpdateTimeTask); 81 mHandler.postDelayed(mUpdateTimeTask, 100); 82 } 83 } 84};</pre> 85 86<p>A couple of things to take note of here. First, the event doesn't 87have a .getWhen() method on it, which we handily used to set the start 88time for the timer. Instead, we grab the System.currentTimeMillis(). 89Also, the Handler.postDelayed() method only takes one time parameter, 90it doesn't have a "repeating" field. In this case we are saying to the 91Handler "call mUpdateTimeTask() after 100ms", a sort of fire and forget 92one time shot. We also remove any existing callbacks to the handler 93before adding the new handler, to make absolutely sure we don't get 94more callback events than we want.</p> 95 96<p>But we want it to repeat, until we tell it to stop. To do this, just 97put another postDelayed at the tail of the mUpdateTimeTask run() 98method. Note also that Handler requires an implementation of Runnable, 99so we change mUpdateTimeTask to implement that rather than extending 100TimerTask. The new clock updater, with all these changes, looks like 101this:</p> 102 103<pre>private Runnable mUpdateTimeTask = new Runnable() { 104 public void run() { 105 final long start = mStartTime; 106 long millis = SystemClock.uptimeMillis() - start; 107 int seconds = (int) (millis / 1000); 108 int minutes = seconds / 60; 109 seconds = seconds % 60; 110 111 if (seconds < 10) { 112 mTimeLabel.setText("" + minutes + ":0" + seconds); 113 } else { 114 mTimeLabel.setText("" + minutes + ":" + seconds); 115 } 116 117 mHandler.postAtTime(this, 118 start + (((minutes * 60) + seconds + 1) * 1000)); 119 } 120};</pre> 121 122<p>and can be defined as a class member field.</p> 123 124<p>The if statement is just a way to make sure the label is set to 12510:06 instead of 10:6 when the seconds modulo 60 are less than 10 126(hopefully String.format() will eventually be available). At the end of 127the clock update, the task sets up another call to itself from the 128Handler, but instead of a hand-wavy 200ms before the update, we can 129schedule it to happen at a particular wall-clock time — the line: start 130+ (((minutes * 60) + seconds + 1) * 1000) does this.</p> 131 132<p>All we need now is a way to stop the timer when the stop button 133is pressed. Another button listener defined like this:</p> 134 135<pre>OnClickListener mStopListener = new OnClickListener() { 136 public void onClick(View v) { 137 mHandler.removeCallbacks(mUpdateTimeTask); 138 } 139};</pre> 140 141<p>will make sure that the next callback is removed when the stop button 142is pressed, thus interrupting the tail iteration.</p> 143 144<p>Handler is actually a better choice than Timer for another reason 145too. The Handler runs the update code as a part of your main thread, 146avoiding the overhead of a second thread and also making for easy 147access to the View hierarchy used for the user interface. Just remember 148to keep such tasks small and light to avoid slowing down the user 149experience.</p> 150 151 152