In revamping one of my Games on the Android Market, I wanted to add an onscreen timer. The built-in Chronometer class does 95% of what I needed, but getting that extra 5% was annoying enough that I decided to post my solution.
The OOTB Chronometer keeps tracks of time, but it can’t tell you the number of seconds elapsed since it started. This is easily remedied, as I discovered from the fine people on StackOverflow. However, simply getting the elapsed time is not enough because the Chronometer will reset itself everytime the app is Paused or Killed. The two cases are quite different. When the app is Paused, it is still alive and may resume (ex. an incoming call). When the app is Killed, it needs to store the elapsed time somewhere so it can be recovered when the app is Restored (ex. orientation change, or b/c the OS needs memory).
So here is a stripped down solution that seems to do the job:
import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.os.SystemClock;
import android.util.Log;
import android.view.Menu;
import android.widget.Chronometer;
public class CustomChronometerActivity extends Activity {
private static final String TAG = "CustomChronometerActivity";
private static final String MS_ELAPSED = "com.etc.etc.MsElapsed";
private MyChronometer chrono;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//start the chronometer
chrono = new MyChronometer(this);
chrono.start();
setContentView(chrono);
}
@Override
protected void onPause() {
Log.i(TAG, "onPause()");
super.onPause();
chrono.stop();
}
@Override
protected void onResume() {
Log.i(TAG, "onResume()");
super.onResume();
chrono.start();
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
Log.i(TAG, "onSaveInstanceState()");
chrono.stop();
outState.putInt(MS_ELAPSED, chrono.getMsElapsed());
}
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
Log.i(TAG, "onRestoreInstanceState()");
int ms = savedInstanceState.getInt(MS_ELAPSED);
chrono.setMsElapsed(ms);
chrono.start();
}
class MyChronometer extends Chronometer {
public int msElapsed;
public boolean isRunning = false;
public MyChronometer(Context context) {
super(context);
}
public int getMsElapsed() {
return msElapsed;
}
public void setMsElapsed(int ms) {
setBase(getBase() - ms);
msElapsed = ms;
}
@Override
public void start() {
super.start();
setBase(SystemClock.elapsedRealtime() - msElapsed);
isRunning = true;
}
@Override
public void stop() {
super.stop();
if(isRunning) {
msElapsed = (int)(SystemClock.elapsedRealtime() - this.getBase());
}
isRunning = false;
}
}
}
Certainly there are other ways to do this – either by implementing your own timer using Threads or Handlers, or perhaps by implementing an OnChronometerTickListener and subscribing to events. I rather like this solution, but if you’re the clever sort and see some situation where this doesn’t work or some reason why it might be a bad idea, please let me know.