End SurfaceView and GameThread on exiting app

2019-05-01 12:43发布

问题:

I've got a surfaceview and a gameThread class. The gameThread keeps updating and painting on the SurfaceView class.

Now when I exit the app (By pressing the home or back button) I get a message that the app force closed. That's because the GameThread still tries to draw on the erased surfaceview...

So how do i properly end the app without getting this force close notification? I would like the GameThread class to stop when the back button is pressed. It should pause when pressing on home and running in the background. When re-entering the still running game it should resume....

Any ideas?

This is my GameThread class:

public class GameThread extends Thread{

private GameView view;
public boolean isRunning = false;

public GameThread(GameView view) {
    this.view = view;
}

public void setRunning(boolean setRunning) {
    isRunning = setRunning;
}

public void run() {
    while(isRunning) {
        Canvas c = null;
        view.update();
        try {
            c = view.getHolder().lockCanvas();
            synchronized (view.getHolder()) {
                view.draw(c);
            }
        }finally {
            if(c != null) {
                view.getHolder().unlockCanvasAndPost(c);
            }
        }
    }
}

It keeps updating my GameView class:

public class GameView extends SurfaceView{

private GameThread gameThread;

public GameView(Context context, Activity activity) {
    super(context);
    gameThread = new GameThread(this);
    init();
    holder = getHolder();
    holder.addCallback(new SurfaceHolder.Callback() {

        @Override
        public void surfaceDestroyed(SurfaceHolder holder) {

        }
        @Override
        public void surfaceCreated(SurfaceHolder holder) {
            gameThread.setRunning(true);
            gameThread.start();

        }
        @Override
        public void surfaceChanged(SurfaceHolder holder, int format, int width,
                int height) {
        }
    });
}


public void init() {

}

public void update() {

}

public void draw(Canvas canvas) {
    super.draw(canvas);

}
}

When pressing home my logcat shows this up:

02-24 18:24:59.336: E/SurfaceHolder(839): Exception locking surface
02-24 18:24:59.336: E/SurfaceHolder(839): java.lang.IllegalStateException: Surface has already been released.
02-24 18:24:59.336: E/SurfaceHolder(839):   at android.view.Surface.checkNotReleasedLocked(Surface.java:437)
02-24 18:24:59.336: E/SurfaceHolder(839):   at android.view.Surface.lockCanvas(Surface.java:245)
02-24 18:24:59.336: E/SurfaceHolder(839):   at android.view.SurfaceView$4.internalLockCanvas(SurfaceView.java:872)

Main activity:

public class MainActivity extends Activity {

private RelativeLayout relativeLayout;
private GameView gameView;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    gameView = new GameView(this, this);
    setContentView(R.layout.activity_main);
    relativeLayout = (RelativeLayout)findViewById(R.id.mainView);
    relativeLayout.addView(gameView);
}

@Override
public void onBackPressed() {
    // TODO Auto-generated method stub
    super.onBackPressed();
    gameView.onBackPressed();
}

@Override
public boolean onCreateOptionsMenu(Menu menu) {
    // Inflate the menu; this adds items to the action bar if it is present.
    getMenuInflater().inflate(R.menu.main, menu);
    return true;
}

}

回答1:

The Surface underlying the SurfaceView gets destroyed between onPause() and onDestroy() in the app lifecycle. Send a message to your renderer thread in onPause(), and wait for the thread to shut down.

For an example, see the "Hardware scaler exerciser" activity in Grafika. It starts the render thread in the SurfaceHolder's surfaceCreated() callback.

Update: it felt weird to start the thread in surfaceCreated() and stop it in onPause(), so after a bit of reshuffling HardwareScalerActivity now stops the thread in surfaceDestroyed(). (The SurfaceView documentation is a bit ambiguous -- it says the Surface is valid "between surfaceCreated() and surfaceDestroyed()" -- but looking at the SurfaceView implementation this appears to be the expected usage.)

Update 2: I updated the code some more after finding cases it didn't handle well. There's now a 60-line megacomment in the sources, which can also been seen in my answer to a similar question.

Update 3: The mega-comment turned into an architecture doc appendix. Grafika's "hardware scaler exerciser" activity demonstrates approach #2, while "texture from camera" demonstrates approach #1.



回答2:

Well you can make changes to your code like this.

In GameView

@Override
    public void surfaceCreated(SurfaceHolder holder) {
        MainActivity obj=new MainActivity();
        stop=obj.getBoolean(); //receive status of boolean from main activity

        //stop is boolean set if backPressed in main activity
        if(!stop){ 
        gameThread.setRunning(true);
        gameThread.start();
        }
        else
            gameThread.setRunning(false);
    }

In MainActivity

public Boolean getBoolean(){
return stop;    
} 

public void setBoolean(Boolean bools){
stop=bools;
}

@Override
public void onBackPressed() {
// TODO Auto-generated method stub
super.onBackPressed();

stop=true;
setBoolean(stop);

try{
Thread.sleep(200); // Be safeside and wait for Game thread to finish
}
catch(Exception e){
e.printStackTrace();
}

MainActivity.finish();
}

Call setBoolean once before backPress else it will give error. Make amendments to code as per your needs.

Hope it helps. Cheers. :)