Search code examples
javaandroidandroid-canvassurfaceviewgame-loop

Android canvas game loop breaks onResume() after closing and reopening app


On a fresh install, the first time I open the app, I don't really have any problems. However, If I were to kill the app via processes, force stop, or simply press the home button and re launch the app, I run into a few problems...

First problem - when the app is relaunched after closed or killed, there is a long delay before actually launching the app window. Also, sometimes it takes up to 30 seconds for the game graphics (whats being drawn to the canvas) to display. All I will see is a black screen while I hear the game sound effects playing in the background, as if it were running.

Second problem - After waiting about 30 seconds for the app to actually display the canvas, sometimes the on touch listener gets unregistered or something. I'm unable to interact with the game as if my touches were not registering. Once again, this only happens on a RELAUNCH after the app has successfully ran once and then closed / killed.

Third problem - in the case that I get impatient and do not wait the 30 seconds for the canvas to display, but instead tap the home button or kill the "unresponsive" app, I always get an error message saying something like "app unresponsive, wait? force close?" or "Unexpected error, App has stop running."

So these problems have led me to believe it is an issue with how I am starting / restarting / creating my game loop:

public class MainMenuActivity extends ActionBarActivity implements View.OnTouchListener{


MenuView menu;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    menu = new MenuView(this);
    menu.setOnTouchListener(this);
    setContentView(R.layout.activity_main_menu);

    Button buttonplay = (Button)findViewById(R.id.buttonPlay);
    buttonplay.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            launchCanvas();
        }
    });
}

public void launchCanvas(){
     setContentView(menu)
}


@Override
public boolean onTouch(View v, MotionEvent event) {
         ////do stuff
    }
    return true;
}


////////////////////////////////////////inner MenuView class

public class MenuView extends SurfaceView implements Runnable {

    Thread t = null;
    SurfaceHolder holder;
    boolean ok;
    Game game;

    public MenuView(Context context){
        super(context);
        game = new Game(this);
        holder = getHolder();
    }

    @SuppressLint("WrongCall")
    @Override
    public void run() {
        while(ok){
            if(!holder.getSurface().isValid()){
                continue;
            }

            Canvas c = holder.lockCanvas();
            onDraw(c);
            holder.unlockCanvasAndPost(c);
        }
    }


    public void onDraw(Canvas canvas){
        canvas.drawARGB(255, 1, 5, 29);
        game.draw(canvas);
    }

    public void pause(){
        ok = false;
        while(true){
            try{
                t.join();
            }catch (InterruptedException e){
                e.printStackTrace();
            }
            break;
        }
        t=null;
    }
    public void resume(){
        ok = true;
        t = new Thread(this);
        t.start();
    }



    }
////////////////////////////////////////////////////end MenuView class`



@Override
protected void onPause() {
    super.onPause();
    menu.pause();
}

@Override
protected void onResume() {
    super.onResume();
    menu.resume();
}

}

Any ideas on how to fix?

UPDATE - Potential Problem Discovered I believe the root of my problems came from not letting my thread sleep. I added the following try block in the draw() method that is called every iteration of the thread loop:

try {
    Thread.sleep(10);
} catch (InterruptedException e) {
    e.printStackTrace();
}

Although it is a bit choppy and uneven now, I have not been able to reproduce the problems described above.

I just changed from 10ms to sleep only 1ms, and It isn't choppy anymore AND still cannot reproduce the problems. 1ms seems kind of trivial... why is this necessary for the thread to sleep an insignificant amount of time?


Solution

  • My own SurfaceView-based app has a separate Thread in which two methods run: update() and draw(). However, the draw() method only runs if canvas != null. This is all the code I have related to pausing and resuming, apart from having a surfaceCreated(SurfaceHolder sh) {...}, which runs this code:

    if(!thread.isAlive()) {
        thread.setRunning(true);
        thread.start();
    }
    

    If this doesn't help you, relevant parts from my entire framework:

    Main.java:

    sets MainView as the content view (setContentView(mainView))

    MainView.java:

    @Override
    public void surfaceCreated(SurfaceHolder surfaceHolder) {
        Log.d(TAG, "Surface created");
        tf = Typeface.create("Roboto", Typeface.BOLD);
        recalc=true;
        if(!thread.isAlive()) {
            thread.setRunning(true);
            thread.start();
        }
    }
    

    MainThread.java:

    public class MainThread extends Thread {
        // (abridged) Game logic loop
        @Override
        public void run() {
            Canvas canvas;
            Log.d(TAG, "Starting game loop");
            while (running) {
                canvas = null;
                // try locking the canvas for exclusive pixel editing
                // in the surface
                try {
                    canvas = this.surfaceHolder.lockCanvas();
                    synchronized (surfaceHolder) {
                        // update game state
                        mainView.update();
                        // render state to the screen
                        if (canvas != null) mainView.render(canvas); // canvas == null when activity is in background.
                        // ... my code then handles sleeping, etc. for fixed fps.
                    }
                } finally {
                // in case of an exception the surface is not left in
                // an inconsistent state
                if (canvas != null) {
                    surfaceHolder.unlockCanvasAndPost(canvas);
                }
            }
        }
    }
    

    Hopefully this makes sense to you! You should be able to adapt it to your own project as necessary.