Maarten Pennings'
Android development: Timers
fampennings / maarten / android / 04timers

Timers

Introduction

This article is about generating timer ticks, for example as the timing basis for a simple action game.

The Android SDK comes with a Timer class. So it was a natural, as a first step, to try using that one. But it's a bad idea as we will see later. The Timer class creates an fresh thread. And then we get into multi threading problems with main thread. The result was writing our own timer, a simpler timer STimer, that does not create a fresh thread.

Note that there is some overlap between the two chapters (timer and stimer), especially on explaining the queues. This allows to read the chapters independently.

1 Timer

[Download the source of this approach]

We start with developing the activity, for some basic UI to test the timer. Next we add the timer.

1.1 Activity

We start with developing the activity. It has a start button and a stop button. With these buttons, the user can start respectively stop the timer. When the timer runs, it increments a counter, and shows the current value in a text view.

public class TimerActivity extends Activity {

    TextView mTextView;
    Button   mButtonStart;
    Button   mButtonStop;

    int      mCount;

    protected void TheTimerTask() {
        mCount++; 
        mTextView.setText("Count="+mCount);
    }
    
    OnClickListener mButtonStart_OnClick = new OnClickListener() {
        @Override public void onClick(View v) {
            mCount= 0;
            timerStart();
        }
    };
    
    OnClickListener mButtonStop_OnClick = new OnClickListener() {
        @Override public void onClick(View v) {
            timerStop();
        }
    };

    
    @Override public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        
        mTextView= (TextView)findViewById(R.id.textview);
        mButtonStart= (Button)findViewById(R.id.buttonstart);
        mButtonStop= (Button)findViewById(R.id.buttonstop);

        mButtonStart.setOnClickListener(mButtonStart_OnClick);
        mButtonStop.setOnClickListener(mButtonStop_OnClick);

    }
}

The code is straightforward. The xml layout file declares a text view with id "textview" and two buttons with ids "buttonstart" and "buttonstop". The class TimerActivity declares a member variable for them, and its constructor finds them (findViewById). Furthernore, the class contains two member variables mButtonStart_OnClick and mButtonStop_OnClick that contain click listeners for the start button respectvely the stop button.

What we now need to work on is adding the timer; its start and stop function, and its periodic task. This should make the red code work. A word of warning, the blue function TheTimerTask will undergo numerous transformations during this article.

1.2 Timer

To create a standard Android timer, simply call Timer t= new Timer(). To have the timer do a periodic action, set its schedule via t.schedule(task,delay,period). This will have the timer wait delay milliseconds before first "execution", and after that wait period milliseconds between subsequent "executions". The hard part is that the timer does not have a simple listener that gets called every "execution".

Timers are used to schedule jobs running in the background. A timer has a single thread for this. And "execution" means running a function on this thread. The task parameter in the schedule function represents this function. To be precise, the task parameter should be an object of the rather empty class TimerTask, which implements the interface Runnable, which has a sole function run. It is this run function that gets called every "execution".

We must override this run function. We could do that with an inline (anonymous class) as exlained in another article. But to make the code clearer in this article, we create an helper class TheTimerTask. This is the first revision for the TheTimerTask activity method; it is turned into an inner class of the actvity, with a run method that does the work.

Timer    mTimer;

protected class TheTimerTask extends TimerTask {
    @Override public void run() {
        mCount++; 
        mTextView.setText("Count="+mCount); // WRONG
    }
}

protected void timerStart() {
    mTimer = new Timer();
    mTimer.schedule( new TheTimerTask(), 0, 100 ); // 100 milli seconds
}

protected void timerStop() {
    mTimer.cancel(); 
}

As the red comments indicates, we have a bug here. Before we solve that, let's make the timer code more robuust.

To make the start and stop calls work in any context, we add the invariant that mTimer==null when the timer is not running. By the way, calling the cancel method on a timer cancels the timer, removes any scheduled tasks and terminates the thread. If there is currently a running task it is not affected, but no more tasks may be scheduled on this timer.

protected void timerStart() {
    if( mTimer==null ) {
        mTimer = new Timer();
        mTimer.schedule( new TheTimerTask(), 0, 100 ); // 100 milli seconds
    }
}

protected void timerStop() {
    if( mTimer!=null ) {
        mTimer.cancel();
        mTimer= null;
    }
}

Note the last blue line (mTimer= null), it records that the timer s cancelled so that a next start will create a fresh one.

1.3 Multi threading

If we run the code from the previous section we get "Only the original thread that created a view hierarchy can touch its views." as soon as we touch the start button.

It is clear that this happens. The textview is created (and thus managed) by the main thread, so we can not update it (setText) from another thread, like the one created by the timer.

There is a standard way to solve that. We can put a message in the queue of the main thread. When that message gets executed, it is executed by the main thread, so it can access the textview. Let look at that in detail.

When an application is started, it gets a fresh thread, known as the main thread. The main thread is dedicated to running a message queue that takes care of managing the activity. Each thing that happens to an app, such as a touch on a button, causes a message to be enqueued. The thread of the app dequeues messages and executes a piece of code dedicated to that message. In this example the piece of code that handles a touch message would make the button orange for a moment and call the button's on click listener.

As mentioned before, it is also possible to put a message in the queue ourselves. This is somewhat tricky, because which piece of code should handle that message? The solution is that the message we enqueue must be an object that implements the standard android interface Runnable.

public interface Runnable {
    public void run();
}

In the latest revision of the function TheTimerTask it runs on the timer thread, so it can not touch the textview. We revise it again; TheTimerTask will now post a message to the main queue. This message is an object that implements the run function of the Runnable interface to do the actual work.

Runnable TheDelegatedTimerTask= new Runnable() {
    @Override public void run() { // function called by main thread when posted message is dequeued
        mCount++; 
        mTextView.setText("Count="+mCount);
    }
};

protected class TheTimerTask extends TimerTask {
    @Override public void run() { // function called by timer task when timer expires
        // What we want to say is
        //  mCount++; 
        //  mTextView.setText("Count="+mCount);
        // But this gives "Only the original thread that created a view hierarchy can touch its views."
        ... post TheDelegatedTimerTask in the queue of the main thread ...
    }
}

In order to put a message in a thread's queue, we need access to it. A queue is managed by a queue handler, which is typed Handler. So, a Handler allows us to enqueue messages (Runnable objects) in the thread's queue. Each Handler is associated with a single thread; when we create a new Handler, it is automatically bound to the queue of the thread that is creating it.

The code below shows that the timer has a member mHandler. When the activity is created, the mHandler will be new'ed and thus be a reference to the handler of the queue of the activity. The code below now also shows the final body of TheTimerTask.

Handler  mHandler= new Handler();

protected class TheTimerTask extends TimerTask {
    @Override public void run() {
        // What we want to say is
        //  mCount++; 
        //  mTextView.setText("Count="+mCount);
        // But this gives "Only the original thread that created a view hierarchy can touch its views."
        mHandler.post(TheDelegatedTimerTask);
    }
}

1.4 Looking back

When we create a timer we implicitly create its thread. So, our activity has now two threads: the main thread and the timer thread. When there's more than one thread, we get thread synchronisation issues.

Maybe that locks and synchronized can help.

2 Simpler timer

[Download the source of this approach]

If the simplicity of the timer sounds good, but the extra thread doesn't, you're at the right place. In this section, we are going to write our own simpler timer class. It's even simpler than you'd guess. The reason is that Android has a built-in feature of sending yourself a message — with a delay!

We are going to develop a simple time class called STimer. The timer will not be one-shot, rather if will be periodic. We refer to the periodic timer expirations as alarms. The user of the timer can handle alarms by installing a callback, a so-called alarm listener. So we need a period, an enable flag, and we need to store the listener.

2.1 Period

Let's build up our timer class gradually. We start with a simple member, the period of the timer, which has a setter and getter.

public class STimer {

    protected int mPeriod= 100;

    public void setPeriod( int ms ) {
        mPeriod= ms;
    }

    public int getPeriod( ) {
        return mPeriod;
    }

    ...
}

2.2 Listener

The next one is slightly harder. We have a member that records the listener. We need an interface definition for that, and a setter.

public class STimer {

    public interface OnAlarmListener {
        void OnAlarm( STimer source );
    }

    protected OnAlarmListener mOnAlarmListener= null;

    public void setOnAlarmListener( OnAlarmListener l ) {
        mOnAlarmListener= l;
    }

    ...
}

2.3 Enabled

The next one is the hardest, but we hide the hard part for the moment. We have a member that records the enabled state, and a getter and setter for that member. We also add the convenience functions start and stop.

public class STimer {

    protected boolean mEnabled= false;

    public void setEnabled( boolean enabled ) {
        ... hard part hidden ...
    }

    public boolean getEnabled() {
        return mEnabled;
    }

    public void start( ) {
      setEnabled(true);
    }

    public void stop( ) {
      setEnabled(false);
    }

    ...
}

Now comes the hard part.

2.4 Handler

When an application is started, it gets a fresh thread, known as the main thread. The main thread is dedicated to running a message queue that takes care of managing the activities. Each thing that happens to an app, such as a touch on a button, causes a message to be enqueued. The thread of the app dequeues messages and executes a piece of code dedicated to that message. In this example the piece of code that handles a touch message would make the button orange for a moment and call the button's on click listener.

It is also possible to put a message in the queue ourselves. This is somewhat tricky, because which piece of code should handle that message? The solution is that the message we enqueue must be an object that implements the standard android interface Runnable.

public interface Runnable {
    public void run();
}

As soon as the thread dequeues such a message, it simply calls the run() method of the dequeued object.

So, why does this help us? The trick is that when we enqueue a message, we can pass a delay. This allows us to enqueue a message, but tell the thread to delay it with, say, 5 seconds. In the run() function of the message, we will call the listener we associated with the timer (so that it can do the user side of handling the alarm), and then we will enqueue the message again, with a delay of 5 seconds. This way, we get a periodic timer that fires every 5 seconds.

In order to put a message in a thread's queue, we need access to it. A queue is managed by a queue handler, which is typed Handler. So, a Handler allows us to enqueue messages (Runnable objects) in the thread's queue. Each Handler is associated with a single thread; when we create a new Handler, it is automatically bound to the queue of the thread that is creating it.

The code below shows that the timer has a member mHandler. When a timer is created by an Activity, the mHandler will be new'ed and thus be a reference to the handler of the queue of the activity. The code below now also shows the body of setEnabled. When the enabled state becomes true, we post the first message (mMessage, still to be discussed) in the queue, with a delay of mPeriod. This is the red statement. Fortunately, a queue handler also has a feature to remove a message from a queue. This comes in handy when disabling a timer (the blue statement).

public class STimer {

    protected Handler  mHandler= new Handler();
    protected Runnable mMessage= ...;

    public void setEnabled( boolean enabled ) {
        if( enabled!=mEnabled ) {
            // The enabled state really changes
            if( enabled ) {
                // Post the first message (which will post the next message and so on)
                mHandler.postDelayed(mMessage, mPeriod);
            } else {
                // Remove any message that is in the queue
                mHandler.removeCallbacks(mMessage);
            }
            // Record the new state
            mEnabled= enabled;
        }
    }


}

2.5 Message

Now comes the last detail. We have just enqueued a message mMessage, whose run function will be executed by the thread after mPeriod milliseconds. So, we must make sure mMessage has the correct run function. We create a single message object, in the body of the timer. We do this via an inline class extension of the runnable interface.

public class STimer {

    protected Runnable mMessage= new Runnable() {
        @Override public void run()
        {
            if( mOnAlarmListener!=null ) mOnAlarmListener.OnAlarm(STimer.this);
            if( mEnabled )               mHandler.postDelayed(mMessage, mPeriod);
        }
    };

    ...

}

We see the continuation of the timer by reposting the same message (red code).

And we see the notfication of the user via the listener (blue code).

Both parts have one detail. The blue part (notification) should call the listener, bt only when the listener is set. Easy. The red part is somewhat harder. The listener we just called, could have called stop(). This removes all mMessage messages form the queue. However, there are no such message in the queue, because the only one that was present has just been taken out; it is currently being executed. So we need to check if the timer is still enabled before we can post a new delayed message.

That's it. The whole timer class.

2.6 Activity

The activity is straightforward. We have added a textview and two buttons (start and stop) to the layout.

public class STimerActivity extends Activity {

    TextView mTextView; // Remark 1
    Button   mButtonStart;
    Button   mButtonStop;

    int      mCount; // Remark 2
    STimer   mSTimer;

    OnClickListener mButtonStart_OnClick = new OnClickListener() { // Remark 3
        @Override public void onClick(View v) {
            mCount= 0;
            mSTimer.start();
        }
    };

    OnClickListener mButtonStop_OnClick = new OnClickListener() { // Remark 4
        @Override public void onClick(View v) {
            mSTimer.stop();
        }
    };

    OnAlarmListener mSTimer_OnAlarm= new OnAlarmListener() { // Remark 5
        @Override public void OnAlarm(STimer source) {
            mCount++;
            mTextView.setText("Count="+mCount);
            if( mCount==100) source.stop(); // Remark 6
        }
    };

    @Override public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        mTextView= (TextView)findViewById(R.id.textview); // Remark 7
        mButtonStart= (Button)findViewById(R.id.buttonstart);
        mButtonStop= (Button)findViewById(R.id.buttonstop);

        mButtonStart.setOnClickListener(mButtonStart_OnClick); // Remark 8
        mButtonStop.setOnClickListener(mButtonStop_OnClick);

        mSTimer= new STimer(); // Remark 9
        mSTimer.setOnAlarmListener(mSTimer_OnAlarm);
        mSTimer.setPeriod(100);
    }
}

Remark 1 We declare a field per view we have on the activity. We have a textview that will display how many alarms have fired, and a start and stop button to start and stop the timer.

Remark 2 The timer is created programmatically in the onCreate. Furthermore we have a counter counting the number of alarms that have fired.

Remark 3 We create a click handler for the start button. It will reset the counter, and start the timer.

Remark 4 We create a click handler for the stop button. It will stop the timer.

Remark 5 We create an alarm handler for the timer. It will increment the counter, and update the textview to show the new counter value.

Remark 6 When the counter reaches 100, we stop the timer. Note the we use the passed timer (source), which is the same as the timer member (mSTimer). Note also that we stop the timer from in its call back. This is why we needed the if( mEnabled ) in the run() of the mMessage.

Remark 7 We lookup the views (widgets) that have just been created by setContentView.

Remark 8 We set the click handlers for both buttons (as created under remark 3 and 4).

Remark 9 Finally, we create the timer, set its alarm handler (created under remark 5), and set its period to 100 milliseconds.

2.7 Looking back

The STimer class we developed seems a very reusable class. If we want to use this in multiple projects, it does not seem wise to copy the class code into each new project. See the uselib article of how to add a class to the Android eclipse SDK.

3 ScheduledThreadPoolExecutor

The help files say about the Timer class "Prefer ScheduledThreadPoolExecutor for new code". It also says "This class is preferable to Timer when multiple worker threads are needed", so more flexability, but also multi threading issues again.

I have not exprimented with this class yet.


home
Timers
Timers




Downloads:
Timer source
Simple timer source





Editor:
Maarten Pennings
Laatste wijziging:
20 sep 2011
File:
error