Android - How to pause/unpause thread in games

30 replies [Last post]
aywa
User offline. Last seen 7 years 19 weeks ago. Offline
Joined: 12/30/2009

Hello,

First of all I would like to thank you because your blog and the information you provide have been a tremendous help for me so far.
I am a new android developer in the middle of creating a simple game with a SurfaceView and a thread (like the Lunar Lander example).

I noticed then when the surface is destroyed or something happens such as an incoming call or the keyboard orientation is changed, it leaves the application in an unpredictable state and it often times crashes.

I was wondering how you would handle the pausing/unpausing of the thread and game in accordance with the Activity lifecycle.

My main loop looks something like this:

while(threadRunning){
if (surfaceValid){
processInput()
if (mGameState == STATE_RUNNING)
updateGame();
doDraw();

}
}

Right now I create the game thread in the onStart() method and "pause" it immediatley and "resume" it once the Surface is created. The thread stops in the onStop() method of the activity and threadRunning is set to false.
The problem I'm having is when the thread is still running but the game is not in the foreground. I want to keep the game's state but I don't want to busy-wait in the main loop.
The research I did so far suggested that wait() and notify() should be used with synchronization to pause the thread, but how exactly would I go about doing that? Or is there a better way to do it? If so, please enlighten us with your infinite knowledge. :)

Thanks!

Robert Green
User offline. Last seen 24 weeks 2 days ago. Offline
Joined: 02/18/2009
Thanks for the compliments!

Thanks for the compliments! I'm glad that my information is helping you out.

Pausing can be a little tricky at first. I have a isPaused flag in my thread - this allows me to keep the paused state separate from the gamestate - you can pause at any point in any gamestate and resume without a hitch. Then my mainloop checks for the paused flag and basically just sleeps for 100ms increments until the pause is lifted. This is easy on the CPU.

I pause the thread and renderer when the activity onPause is called and resume when onResume is called. I set isRunning to false when onDestroy is called and release all resources then. That will make my thread exit the loop.

My main loop looks like:

@Override
public void run() {
// run our thread on high priority to keep from getting slowdown
Process.setThreadPriority(Process.THREAD_PRIORITY_URGENT_DISPLAY);
// wait for surface to become available
while (!isSurfaceCreated && isRunning) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
}
}
// Set the last tick to right now.
world.lastTickMs = System.currentTimeMillis();
while (isRunning) {
processInput();
while (world.isPaused && isRunning) {
try {
processInput();
Thread.sleep(100);
} catch (InterruptedException e) {
}
// coming out of pause, we don't want to jump ahead so we have to set the last tick to now.
world.lastTickMs = System.currentTimeMillis();
}
long msSinceLastTick = System.currentTimeMillis() - world.lastTickMs;
if (msSinceLastTick >= TICK_DELAY) {
try {
synchronized (tickMutex) {
world.getLock();
update();
if (pauseAfterTick) {
pauseAfterTick = false;
doPause();
}
}
} catch (Throwable t) {
throw new RuntimeException(t);
} finally {
world.releaseLock();
}
} else {
try {
// sleep until the next tick time.
Thread.sleep(TICK_DELAY - msSinceLastTick);
} catch (InterruptedException e) {
}
}
}
cleanUp();
}

/**
* The Main Update routine
*
* @return
*/
private boolean update() {
if (!world.isPaused) {
long now = System.currentTimeMillis();
World world = this.world;
// single player or MP host
world.curTickMs = now;
world.tickDelta = now - world.lastTickMs;
// camera first because the other updates will use it to determine vis
updateCamera();
updatePlayers();
updateLevel();
updateMapObjects();
updatePlayerCollisions();
updateItems();
updateNPCs();
updateState();
world.lastTickMs = world.curTickMs;
int tickTime = (int) (System.currentTimeMillis() - now);
if (tickTime > TICK_DELAY) {
// Log.d(TAG, "Tick time = " + (System.currentTimeMillis() - now));
}
world.tickDuration = tickTime;
return true;
}
return false;
}

You could use wait and notify to truly pause the thread if you wanted to. I keep it looping and sleeping 99.9% of the time which works fine but probably isn't quite ideal. wait and notify are standard java things. I think you could have the loop start by checking for the isPaused flag and if it exists, wait on a locking object. Then you have your activity notify the locking object onResume or onDestroy or anything that affects that thread which would make you want to have the thread finish up and do its clean-up routine.

Try it out - let me know how it works!

aywa
User offline. Last seen 7 years 19 weeks ago. Offline
Joined: 12/30/2009
Thanks

Hey,

Thanks for all the help. I ended up implementing the method you showed here. It seemed simpler than using wait and notify to pause the thread. It is working great now. :)
Your right, it is a bit tricky at first to implement pausing, especially since the Lunar Lander example does not implement it properly.

Thanks for your help!

jhietterbug
User offline. Last seen 5 years 45 weeks ago. Offline
Joined: 09/07/2009
Hi! It's us again. I just

Hi! It's us again. I just realized that we still haven't completely dealt with the weird lifecycle issues we've been having months ago. We got to a point in which we could run the game okay, but there just were times when it would suddenly crash. I guess it's about time we fix this for good. XD

I have a question with regards to this block of code, if you don't mind. :)

try {
synchronized (tickMutex) {
world.getLock();
update();
if (pauseAfterTick) {
pauseAfterTick = false;
doPause();
}
}
} catch (Throwable t) {
throw new RuntimeException(t);
} finally {
world.releaseLock();
}

Why do we need a tickMutex here, as well as world.getLock()/releaseLock()?

Also, is there any way that we could maybe get our hands on a working source for a sample app that implements pause/resume (with threads), among other things to keep it stable and without the bugs. We'd greatly appreciate any help! :D

Robert Green
User offline. Last seen 24 weeks 2 days ago. Offline
Joined: 02/18/2009
It's pretty

It's pretty straightforward.

You'll need to respond to onPause and onResume in your activity. onPause needs to pause the thread and prepare for the surface to go away. onResume should resume your thread and there may be a new surface that comes with it. You never know what you're going to get! :)

The tickMutex is to make it so that variables don't change during the tick, such as input and settings. Typically, you want the update to handle everything, then new data can be fed in, then another update occurs. Synchronizing on that one object will allow for that to happen.

I use a lock on the world to synchronize it with the render thread. If you don't render in another thread, you don't need that.

I recommend putting log statements on the lifecycle methods so you can see when they are getting called and what your code is doing in response.

jhietterbug
User offline. Last seen 5 years 45 weeks ago. Offline
Joined: 09/07/2009
Hi Sir Robert! We finally got

Hi Sir Robert! We finally got to 'pause' and 'unpause' the game thread and all! Thanks!! Still, just a few more clarifications, if you don't mind:

Here are our codes for onResume onPause, and onDestroy, respectively. It's working for us so far, but hopefully you could take a look and see if we're doing something there that we're not supposed to.

(1) I checked back on one of your replies where you wrote the following:

onResume - easy. Is your thread still alive and waiting to be resumed? If so,
you're in a game. If not, create the thread and initialize the new game :)

As you can see in our onResume, we're simply checking if isPaused is set to true or not. Is this what you meant by checking if the thread is still alive? :D Also, is it okay if we put the thread initialization/creation at the SurfaceView's constructor?

protected void onResume() {
super.onResume();
Log.d("DEBUG", "onResume");
if(gameThread.isPaused==true) {
Log.d("DEBUG", "RESTART");
gameThread.unPause();
}
else {
Log.d("DEBUG", "START");
gameThread.setRunning(true);
gameThread.start();
}
}

(2) The only thing that pause() here does in the gameThread is to set isPaused to true. Should we be doing anything else here?

protected void onPause() {
super.onPause();
Log.d("DEBUG", "onPause");
gameThread.pause(); // pause game when Activity pauses
}

(3) I got this piece of code from surfaceDestroyed() in the LunarLander example. Are all these lines necessary? Or is simply calling gameThread.setRunning(false); enough?

protected void onDestroy() {
super.onDestroy();
Log.d("DEBUG", "onDestroy");
boolean retry = true;
if(gameThread!=null) {
gameThread.setRunning(false);
while (retry) {
try {
gameThread.join();
retry = false;
} catch (InterruptedException e) {
}
}
gameThread = null;
}
}

Robert Green
User offline. Last seen 24 weeks 2 days ago. Offline
Joined: 02/18/2009
That's basically what I do.

That's basically what I do. Just throw some log statements in your thread and in a few other key spots then try all of the things on the phone like hitting the phone call button during a game, returning the game, exiting the game, relaunching the game, etc.. and make sure you're getting the results you think you should be getting.

jhietterbug
User offline. Last seen 5 years 45 weeks ago. Offline
Joined: 09/07/2009
Will do! Thanks again! :D

Will do! Thanks again! :D

Anonymous
Canvas becoming Null?

Hi there. I'm working on an Android game which uses a thread and a surfaceview, and I practically based it on the codes you have here on your site. Thank you! However, I'm encountering this very strange problem with regards to the canvas.. I say strange because it doesn't always happen. In fact it only happens maybe 2 out of 10 times I guess.

For some reason when I would pause the game or exit from the activity with the thread + surfaceview (btw I'm able to handle pause/resume quite ok, except for this bit), I sometimes get a NullPointerException error. :O And when I check Logcat it always shows that it was caused by a line in the thread which tries to draw on the canvas (e.g. canvas.drawBitmap(...); or canvas.drawARGB(...);).

Do you have any idea what kind of issue is going on here and how I may be able to solve it? Any help would be good. :D

Robert Green
User offline. Last seen 24 weeks 2 days ago. Offline
Joined: 02/18/2009
This sounds like a

This sounds like a synchronization issue to me. Figure out exactly what's null by putting in some logging statements there. Make sure not to set important things to null while your thread is still running! You need to stop it first. If you want the thread to finish doing the current tick/frame, look into sychronizing it with the call to stop/destroy it.

jhietterbug
User offline. Last seen 5 years 45 weeks ago. Offline
Joined: 09/07/2009
Good to know I'm not the only

Good to know I'm not the only one having this issue with the surfaceview. :P

I also get the null pointer exceptions, but I think I've narrowed things down to the *.lockCanvas() method of the surfaceholder returning null whenever I'd resume or leave an activity. Here's a simplified version of my update code, which is part of the main loop (largely based on the example you have here that handles pause/resume's well):

01 private void update() {
02 ..... Canvas c = null;
03 ..... try {
04 .......... c = mSurfaceHolder.lockCanvas(null);
05 .......... if(c==null) Log.d("DEBUG", "CANVAS IS NULL");
06 ............... while(c==null) {
07 .................... try {
08 ......................... Thread.sleep(50);
09 .................... } catch (InterruptedException e) {}
10 .................... c = mSurfaceHolder.lockCanvas(null);
11 ............... }
12 ............... synchronized (mSurfaceHolder) {
13 .................... drawNow(c);
14 ............... }
15 ..... } finally {
16 .......... if (c != null) {
17 ............... mSurfaceHolder.unlockCanvasAndPost(c);
18 .......... }
19 ..... }
20 }

There are times that I'd get a null canvas after calling lockCanvas, but other times it doesn't. My solution (lines 6-11) was to check if c is null, and wait until lockCanvas returns a non-null canvas. However I'm not sure if I actually solved anything here, or if I'm looking at the wrong parts.

When I tried to put Log statements all over, I noticed that surfaceCreated and surfaceChanged get called in late, perhaps that's why the thread proceeds/resumes but encounters an error once it tries to get the canvas. I also remembered what you said about resuming a thread and I guess it has something to do with it: onResume should resume your thread and there may be a new surface that comes with it. You never know what you're going to get! :)

We'd like to know what you think about this. Thanks, Sir Robert!

Robert Green
User offline. Last seen 24 weeks 2 days ago. Offline
Joined: 02/18/2009
I think what you're

I think what you're experiencing is your game thread trying to draw to a canvas on a surface that has been destroyed. That may not be the case but is my gut feeling. Throw some log statement on the onSurfaceDestroyed method and see if that's getting called right before you have the NPE. If that's the case, the problem then is that you simply are not shutting your thread down before the surface is being disposed of. It could be something else but that's what my gut tells me.

jhietterbug
User offline. Last seen 5 years 45 weeks ago. Offline
Joined: 09/07/2009
I see. I checked thru the

I see. I checked thru the Logcat but it doesn't look like that the surface get destroyed before the thread stops (causing the NPE). Nevertheless, I'd have to agree with your gut-feeling. I guess we're simply missing out on something right here. Do you have any suggestions on how it would be best to handle the surface and the thread so that it (1) starts only once the surface has been created (the quick hack that I did, which I posted earlier, had me busy-waiting until canvas becomes non-null), and (2) ends right before the surface gets destroyed?

Robert Green
User offline. Last seen 24 weeks 2 days ago. Offline
Joined: 02/18/2009
Well, I'd have to recommend

Well, I'd have to recommend figuring out what's null before moving forward. Log everything right there and keep trying to reproduce it until it happens, then examine your log and make your determination. Once you know what's null, you will be able to figure out what's causing it and what to do about it.

T-LED
User offline. Last seen 7 years 1 week ago. Offline
Joined: 05/13/2010
Hi Robert, About pausing game

Hi Robert,
About pausing game thread, which way is better:
+If I pause the Game thread and let it run in a loop waiting until I unpause it.
+Or I use wait/notify,
+Or I set isRunning to false, set game thread to null, and when Activity resume, I start a new thread.

Robert Green
User offline. Last seen 24 weeks 2 days ago. Offline
Joined: 02/18/2009
I keep a boolean called

I keep a boolean called pauseAfterTick

at the end of the tick, if pauseAfterTick is set, it pauses.

you can handle the paused state in one of two ways (or more, but here are my two)

if paused and threadAlive, monitor.wait()
then the unpause or destroy all call notify so it can exit

Or you can just loop with a sleep
while paused and threadAlive, sleep(50)

T-LED
User offline. Last seen 7 years 1 week ago. Offline
Joined: 05/13/2010
Thank you.

Thank you.

Anonymous
Thread Interactions?

Robert,

These posts are old but I have found them very useful and need to ask a related question about an issue I am having. I have a working pause functionality using the scheme you have outlined here, but every once in awhile when my OnPause callback is called and thread.pause() is called to set my isPaused to true, my main run loop for my game thread will keep on running for 10-20 seconds until finally isPaused gets set to true and enters the sleep logic.

Do you know what could cause this or what things I could check? I added a small sleep in my run() loop that always gets executed to give other things in the system a chance to run, but this doesn't work and I don't think I want to force my frame rate lower than it can be.

Thanks!
-Greg

Anonymous
Thread Interactions?

Robert,

First let me apologize if I have two posts about the same topic, but I something interesting happened to the captcha the first time and I don't see it posted yet, so here it goes again...

I have implemented the pausing function as you have laid out with thread.pause() being called in OnPause(), but about 10% of the time when I click back to call OnPause, it gets called immediately, but the pause() method within my thread may not execute from anywhere from 10 seconds to over a minute afterwards.

Is there some sort of priority interaction that may be getting screwed up? I have tried forcing a sleep() every iteration in the main run() loop and lowered the priority, but to no avail. I'm not sure what's going on, do you have any insight or ideas for me to try?

Robert Green
User offline. Last seen 24 weeks 2 days ago. Offline
Joined: 02/18/2009
Hmm, it could be a number of

Hmm, it could be a number of things. Can you post any code tidbits around the sensitive areas?

Anonymous
Code Snippets

Here are all the pieces touched during this event:

The onWindowFocusChanged in my SurfaceView:
/**
* Standard window-focus override. Notice focus lost so we can pause on
* focus lost. e.g. user switches to take a call.
*/
@Override
public void onWindowFocusChanged(boolean hasWindowFocus) {
Log.i("Activity", "onWindowFocusChanged");
if (!hasWindowFocus) thread.pause();
}

The onPause() in my Activity:
/**
* Invoked when the Activity loses user focus.
*/
@Override
protected void onPause() {
super.onPause();
Log.i("Activity", "onPaused");
mThread.pause();
}

The pause() method in my game thread within the SurfaceView:
/**
* Pauses the physics update & animation.
*/
public void pause() {
synchronized (mSurfaceHolder) {
//Log.d("Debug", "thread.pause");
isPaused = true;
Log.i("dog", "thread.pause " +isPaused);
}
}

My run function is just executing my update() calls as long as my thread is alive and not paused, and if it is paused and alive - sleeps. Logcat shows isPaused = false and therefore my update calls churning away in my run loop until finally the thead.pause() method is entered.

If the user hits back I let the activity view roll to the previous one so onWindowFocusChanged is called. I have not investigated whether the two different calls to thread.pause() are somehow fighting each other but will try that next.

Thanks in advance for your help!

Anonymous
Negative on the two sources

Negative on the two sources of thread.pause()...issue still remains.

Robert Green
User offline. Last seen 24 weeks 2 days ago. Offline
Joined: 02/18/2009
That's kind of what I

That's kind of what I thought. I think what you might be seeing is another thread holding the lock on mSurfaceHolder, which is preventing entry and causing your hang.

Put a log statement before the synchronized block to confirm.

Anonymous
Yup, awesome that was it! : )

Yup, awesome that was it! : ) Is there any danger with removing the synch block from both the thread.pause() and the onWindowFocusChanged pause since they only set a field in the thread that the run() method will then use?

Robert Green
User offline. Last seen 24 weeks 2 days ago. Offline
Joined: 02/18/2009
Ok so now you know that

Ok so now you know that another thread is busy in a segment of code synchronized by that mutex. The trick now is to figure out why and end it. Is it looping endlessly in a synced block, never allowing entry? Look carefully at everything synced to that mutex and piece together in your head how it all works with entry from the UI thread.

Anonymous
Hi, when I get back my thread

Hi,
when I get back my thread from background I get black screen (my run method always paint sth), don't understand why, I implement pause/resume in my activity in this way:

Thanks in advance, Jero.

@Override
protected void onPause() {
super.onPause();
setGameState(GAME_STATE_PAUSE);
}

@Override
protected void onResume() {

super.onResume();

if (mGameLogic.mIsPaused == true) {
mGameLogic.setGameState(GameLogic.GAME_STATE_RUNNING);
}
else {
mGameLogic.start();
}

};

Anonymous
Can't wait to get involved

Hi - I am really glad to find this. great job!

Anonymous
Very useful

I'm from the old school, coding in procedural languages in single thread environments for windows based games. Android + Java has been a pain in my behind, but thanks to these threads, I'm sussing it all out. I finally sorted out my pause/resume issue today, which now seems extremely simple, but I just wasn't getting all the info I needed from Android's life cycle diagram. The docs are pretty good, but there's nothing like an explained example of real world issues.

Anyway, I found what I needed here (until the next problem pops up! :) ), and I am extremely grateful for your help. It's this kind of internet presence that stops maybe 100s of developers from smashing their heads into desks and turning their brains into mush while they search endlessly for help that often isn't there.

Much respect and thanks again!

-Simon

Anonymous
Nice post

Very nice to read such "old" posts!

This topic, like the little that you can see, they feel accompanied us at the beginning

Anonymous
Hello Everyone

Hi everybody I am a new member. Well I have been lurking around for like the last 3-4 days but I decided to become registered to ask a question. My question is, in like 3 days I will be going to another town and I might not have internet access. I want to be able to still read the site. I just wanted to know may I save all of its pages on a large drive? So that I can read and learn while I have no internet? Are we allowed to do this? I tried checking out the terms and conditions but did not find anything relating to this matter.

Robert Green
User offline. Last seen 24 weeks 2 days ago. Offline
Joined: 02/18/2009
Feel free to save pages for

Feel free to save pages for personal reading on the road - just don't republish them or make them available on another site or via a downloadable archive or anything.

Thanks