Question

I have a surfaceView setup and running, but when I resume it I get an error that the thread has already been started. What's the proper way to handle when the app goes to the background and then back to the foreground? I've tinkered around and managed to get the app to come back without crashing... but the surfaceView doesn't draw anything anymore. My code:

    @Override
    public void surfaceCreated(SurfaceHolder holder) {
           Log.e("sys","surfaceCreated was called.");
           if(systemState==BACKGROUND){
                  thread.setRunning(true);

           }
           else {
        thread.setRunning(true);
               thread.start();
               Log.e("sys","started thread");
               systemState=READY;
           }



    }
    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
           Log.e("sys","surfaceDestroyed was called.");
           thread.setRunning(false);
           systemState=BACKGROUND;
    }
Was it helpful?

Solution

The easy solution is to simply kill and restart the thread. Create methods resume() - creates thread object and starts it - and pause() - kills thread (see Lunarlander example) - in your SurfaceView class and call these from surfaceCreated and surfaceDestroyed to start and stop the thread.

Now in the Activity that runs the SurfaceView, you will also need to call the resume() and pause() methods in the SurfaceView from the Activity's (or fragment's) onResume() and onPause(). It's not an elegant solution, but it will work.

OTHER TIPS

This bug appears to relate to the lunar lander bug, which is quite famous (do a Google search on it). After all this time, and after several android version releases, the bug still exists and no one has bothered to update it. i have found this to work with the least code clutter:

  public void surfaceCreated(SurfaceHolder holder) {     
          if (thread.getState==Thread.State.TERMINATED) { 
               thread = new MainThread(getHolder(),this);
          }
          thread.setRunning(true);
          thread.start();
  }
public void surfaceCreated(SurfaceHolder holder) {
        if (!_thread.isAlive()) {
            _thread = new MyThread(this, contxt);
        }

public void surfaceDestroyed(SurfaceHolder holder) {            
        boolean retry = true;
        _thread.setRunning(false);
        while (retry) {
            try {
                _thread.join();
                retry = false;
            } catch (InterruptedException e) {
                // we will try it again and again...
            }
        }
    }

The best way I have found is to override the onResume method of the activity controlling the surface view so that with the method it re-instantiates the SurfaceView and then sets it with setContentView. The problem with this approach is that you need to reload any state that your SurfaceView was taking care of.

public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(new MyCustomSurfaceView(this));
    }

    @Override
    protected void onResume() {
        super.onResume();
        setContentView(new MyCustomSurfaceView(this));
    }

Tried to comment on the accepted answer above but couldn't, new to this. I don't think you should be calling your start/stop thread methods from both your SurfaceView and Activity. This will result in starting/stopping the thread doubly, and you can't start a thread more than once. Just call your methods from the Activity's onPause and onResume. They're called when exiting and re-entering the app so this will make sure your states are handled properly. surfaceDestroyed isn't always called, which messed me up for a while.

If you use this method make sure to check for a valid surface in your run code before working with your canvas, because the Activity will start the thread in onResume before the surface is available.

        while (_run) {
            if (_surfaceHolder.getSurface().isValid()) {
                ...
            }
        } //end _run

This is what I have used. The app does not crashes now.

View Class:

holder.addCallback(new Callback() {

        public void surfaceDestroyed(SurfaceHolder holder) {
            gameLoopThread.setRunning(false);
            gameLoopThread.stop();
        }

        public void surfaceCreated(SurfaceHolder holder) {
            gameLoopThread.setRunning(true);
            gameLoopThread.start();

        }

In the GameLoopThread :

private boolean running = false;

public void setRunning(boolean run) {
    running = run;
}
@Override
public void run() {
    long ticksPs=1000/FPS;
    long startTime;
    long sleepTime;

while(running){
        Canvas c = null;
        startTime=System.currentTimeMillis();
        try {
            c = view.getHolder().lockCanvas();
            synchronized (view.getHolder()) {

                view.onDraw(c);

            }

        } finally {

            if (c != null) {
                view.getHolder().unlockCanvasAndPost(c);
            }

        }
        sleepTime=ticksPs-(System.currentTimeMillis()-startTime);
        try{

            if(sleepTime>0){
                sleep(sleepTime);
            }
            else
                sleep(10);
        } catch(Exception e){}
}

}

I hope it will help.

You should use the Activities onPause() and onResume() methods.

First, in surfaceCreated(), start the thread. Also, in onResume(), make sure the thread isn't already started (keep a variable inside the thread or something). Then if it is not running, set it as running again. in onPause(), pause the thread. In surfaceDestroyed, pause the thread again.

Another solution for this good-known problem. Sadly, I don't understand why it works -- it came out accidentally. But it works good for me and it's easy to implement: no overriding of Activity's onPause(), onResume(), onStart(), onStop(), nor writing of special thread methods (like resume(), pause()) are required.

Special requirement is to put all changing variables in something other than rendering thread class.

Main points to add to render-thread class:

class RefresherThread extends Thread {
    static SurfaceHolder threadSurfaceHolder;
    static YourAppViewClass threadView;
    static boolean running;

    public void run (){
        while(running){
            //your amazing draw/logic cycle goes here
        }
    }
}

Now, important things about YourAppViewClass:

class YourAppViewClass extends SurfaceView implements SurfaceHolder.Callback  {
    static RefresherThread surfaceThread;

    public YourAppViewClass(Activity inpParentActivity) {
        getHolder().addCallback(this);
        RefresherThread.threadSurfaceHolder = getHolder();
        RefresherThread.threadView = this;
    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        surfaceThread = new RefresherThread();
        surfaceThread.running=true;
        surfaceThread.start();
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        surfaceThread.running=false;
        try {
            surfaceThread.join();
        } catch (InterruptedException e) {  
        }               
    }
}

Two code blocks above are not full-written classes, but mere notion of which commands in which methods are needed. Also note that each return to app invokes surfaceChanged().

Sorry for such space-consuming answer. I hope it will work properly and will help.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top