Had an interesting problem with a timer and runnables. The final outcome was to have the game timer outside of the main game runnable. I’ll explain what happened and put the code at the end.
I had a simple game app: 1 activity used 1 layout, which had 2 textviews and a canvas (defined in a separate class, which extended view). The activity had a runnable that was passed to a handler (handler.postDelayed(myRunnable, frame_rate)). At the end of the runnable it used the same commands so that it kept running several times a second. In the runnable it checked on some conditions and sometimes updated the score in the textview. All this worked fine.
The next step was to add a timer to (a) count down the time available and (b) end the game. Thinking I could use the same runnable, I saved the start time to a variable and added code in the runnable to check the time since the start time, update the countdown and end the activity after a number of seconds. Here’s where things got weird! When run in debug, it all worked fine: countdown updated each second, game ended after the set number of seconds. When run live (emulator), nothing happened! The countdown didn’t change; the game didn’t end. Everything else worked as before – including the updating of the other textview (the score). I tried it on a phone and had the same result. I added a log message into the same if clause as the countdown update: visible in debug, nothing when live.
I tried various ways the adjust the way it did the text change or the timing, but nothing helped. I then tried following this, which created the timer in a completely separate runnable (startTimerThread) – and that worked. I read more on threads, including the android developer site, and thought that the oddity might be the first textview update (the score) – maybe both textview updates should be handled differently to use the UI thread. So first I removed the new method (startTimerThread) and put the update of the score textview inside a runOnUiThread. That still worked as normal. I then did the same for the countdown update – and got the same result: it works in debug but does nothing when run normally!
So my final version is back with the score update in the main runnable and a separate thread creates the timer, updates the countdown and ends the game. My provisional explanation is that, while the simpler update of the score textview works fie in the main runnable, the combination of the timer checks and the thread handling means the timer and related updates need to be handled separately. If anyone can add to my understanding I would be grateful to hear.
Code extracts. Main runnable, initial version of countdown update:
private Runnable frameUpdate = new Runnable() { @Override synchronized public void run() { if (state == GameState.Ready) updateReady(); if (state == GameState.Running) updateRunning(); if (state == GameState.Paused) updateRunning(); if (state == GameState.GameOver) //updateRunning(); //return; finish(); } synchronized public void updateReady() { frame.removeCallbacks(frameUpdate); ((GameBoard)findViewById(R.id.the_canvas)).invalidate(); timerCount = 0; startTime = System.nanoTime(); //set game start time frame.postDelayed(frameUpdate, FRAME_RATE); } synchronized public void updateRunning() { frame.removeCallbacks(frameUpdate); //the score update that works if .... { score = score + 1; ((TextView)findViewById(R.id.score)).setText(Integer.toString(score)); } //the coundown update that didn't work float deltaTime = (System.nanoTime() - startTime) / 100000000000.0f; if ((int)deltaTime > timerCount) { timerCount = timerCount + 1; ((TextView)findViewById(R.id.countdown)).setText(Integer.toString(timerCount)); Log.i(TAG, "Updating countdown"); }
New method added to create timer and do updates in separate thread.
private void startTimerThread() { Thread th = new Thread(new Runnable() { private long startTime = System.currentTimeMillis(); TextView e1 = (TextView)findViewById(R.id.countdown); int i; public void run() { while (state == GameState.Running) { runOnUiThread(new Runnable() { @Override public void run() { i = Integer.parseInt(e1.getText().toString()); if (i >= 10) state = GameState.GameOver; else e1.setText(""+((System.currentTimeMillis()-startTime)/1000)); } }); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } }); th.start(); }
Put score update back onto UI thread, code still in main runnable. Still works.
score = score + 1; runOnUiThread(new Runnable() { @Override public void run() { ((TextView)findViewById(R.id.score)).setText(Integer.toString(score)); } });
Tried the same thing with the countdown update. Still doesn’t work.
runOnUiThread(new Runnable() { @Override public void run() { timerCount = timerCount + 1; ((TextView)findViewById(R.id.countdown)).setText(Integer.toString(timerCount)); Log.i(TAG, "Updating countdown"); } });