Casper Lindberg
Casper Lindberg

Reputation: 680

Android SurfaceView UI Thread Not Working

I have got problems with my Android application (Android ANR keyDispatchingTimedOut). I know the reason behind, but I can't solve the problem. My UI thread is probably overloaded and I wondering how to move expensive code/methods to a background thread. I also wonder, what kind of graphics/update methods can be moved to a background thread?

This is my UI thread:

Canvas canvas;
Log.d(TAG, "Starting game loop");

long beginTime; // The time when the cycle begun
long timeDiff; // The time it took for the cycle to execute
int sleepTime; // ms to sleep (<0 if we're behind)
int framesSkipped; // Number of frames being skipped

sleepTime = 0;

while (running) {
    canvas = null;

    // Try locking the canvas
    try {
        canvas = this.surfaceHolder.lockCanvas();
        if (canvas != null) {
            synchronized (surfaceHolder) {
                beginTime = System.currentTimeMillis();
                framesSkipped = 0; // resetting the frames skipped
                // Update game state here!
                this.gameView.update();
                // Render state to the screen
                // Draws the canvas on the panel
                this.gameView.render(canvas);
                // Calculate how long time the cycle took
                timeDiff = System.currentTimeMillis() - beginTime;
                // Calculate sleep time
                sleepTime = (int) (FRAME_PERIOD - timeDiff);

                if (sleepTime > 0) {
                    try {
                        // Send the thread to sleep for a short period,
                        // very useful for battery saving
                        Thread.sleep(sleepTime);
                    } catch (InterruptedException e) {
                    }
                }

                while (sleepTime < 0 && framesSkipped < MAX_FRAME_SKIPS) {
                    // Need to catch up by updating without rendering
                    // Update game state here!
                    this.gameView.update();

                    // Add frame period to check if in next frame
                    sleepTime += FRAME_PERIOD;
                    framesSkipped++;
                }
            }
        }
    } finally {
        // In case of an exception the surface is not left in
        // an inconsistent state
        if (canvas != null) {
            surfaceHolder.unlockCanvasAndPost(canvas);
        }
    } // End finally
}

And here are the two methods, update() and render()

public void update() {

// Gets the start level if it isn't initialized (when you
// enter the game for the first time)
if (startLvl == 0) {
    startLvl = ((SingleGameActivity)getContext()).getStartLvl();
    currentLvl = startLvl;
}

if (playing) {
    // Update the life icons
    updatePlayerLives();
    updateComputerLives();

    // Checks if you have won or lost
    outOfBounds();

    // Update the position of the ball, player and computer including
    // collision detection and reaction.
    ball.moveWithCollisionDetection(box, player, computer);
    player.moveWithCollisionDetection(box);
    computer.setDirectionWithAI(ball);
    computer.moveWithCollisionDetection(box);

} else {
    // Create new objects
    newObjects();

    if (newRound) {
        playing = true;
        newRound = false;
    }
  }
}

// //////////////////////////////////////////////////////////////////////////////////
// The render() method renders the UI graphics on the screen
public void render(Canvas canvas) {
super.onDraw(canvas);

if (playing) {

    // Draw components
    box.draw(canvas);

    // Draw booster/s
    if (racketTouches > 4) {
        if (b1.isUsed()) {
            b1 = new Booster();

        }
        canvas.drawBitmap(b1.booster, b1.boosterX, b1.boosterY, null);

    }
    if (racketTouches > 14) {
        if (b2.isUsed()) {
            b2 = new Booster();

        }
        canvas.drawBitmap(b2.booster, b2.boosterX, b2.boosterY, null);

    }

    if (racketTouches > 24) {
        if (b3.isUsed()) {
            b3 = new Booster();

        }
        canvas.drawBitmap(b3.booster, b3.boosterX, b3.boosterY, null);
    }           

    // Draw rackets and ball
    player.draw(canvas);
    computer.draw(canvas);
    ball.draw(canvas);

 } else {
    // Draw components
    box.draw(canvas);
    player.draw(canvas);
    computer.draw(canvas);
    ball.draw(canvas);
   }
}

Are the methods overloaded? If so, what can I move to a background thread? And how?

FYI: I'm creating a retro pong/tennis game with boosters included.

Upvotes: 0

Views: 1413

Answers (1)

Zach H
Zach H

Reputation: 257

You never want to do expensive tasks like drawing and animations on the UI thread. There are two ways to utilize a worker thread...

Thread with a runnable (this can be created inside your drawing surface, drawing surface must implement Runnable):

new Thread(new Runnable() {
            public void run() {
                update();
                render();
            }
        }).start();

Extend Thread in a separate class:

class myThread extends Thread {
    Context context;
    //Everything that you currently have in your UI thread should be in here...

    /* Constructor for thread. Pass useful things like context, drawing surface, etc */
    public myThread(Context context) {
        this.context = context;
    }

    public void setRunning(Boolean running) {
        this.running = running;
    }

    @Override
    public void run() {

    Canvas canvas;

    Log.d(TAG, "Starting game loop");

    long beginTime; // The time when the cycle begun
    long timeDiff; // The time it took for the cycle to execute
    int sleepTime; // ms to sleep (<0 if we're behind)
    int framesSkipped; // Number of frames being skipped

    sleepTime = 0;

    while (running) {
        canvas = null;

        // Try locking the canvas
        try {
            canvas = this.surfaceHolder.lockCanvas();
            if (canvas != null) {
                synchronized (surfaceHolder) {
                    beginTime = System.currentTimeMillis();
                    framesSkipped = 0; // resetting the frames skipped
                    // Update game state here!
                    this.gameView.update();
                    // Render state to the screen
                    // Draws the canvas on the panel
                    this.gameView.render(canvas);
                    // Calculate how long time the cycle took
                    timeDiff = System.currentTimeMillis() - beginTime;
                    // Calculate sleep time
                    sleepTime = (int) (FRAME_PERIOD - timeDiff);

                    if (sleepTime > 0) {
                        try {
                            // Send the thread to sleep for a short period,
                            // very useful for battery saving
                            Thread.sleep(sleepTime);
                        } catch (InterruptedException e) {

                        }
                    }

                    while (sleepTime < 0 && framesSkipped < MAX_FRAME_SKIPS) {
                        // Need to catch up by updating without rendering
                        // Update game state here!
                        this.gameView.update();

                        // Add frame period to check if in next frame
                        sleepTime += FRAME_PERIOD;
                        framesSkipped++;
                    }
                }
            }
            } finally {
                // In case of an exception the surface is not left in
                // an inconsistent state
                if (canvas != null) {
                    surfaceHolder.unlockCanvasAndPost(canvas);
                }
            } // End finally
        }
    }         
}

And when you create your drawing surface, assign the thread:

Thread thread;

@Override
public void surfaceCreated(SurfaceHolder holder) {
    thread = new myThread(getContext());

    thread.setRunning(true);
    thread.start();
}

So that's pretty much it - note that you'll probably have to pass some other things to the thread in the second example through its constructor for it to work with your game, such as the surface on which you are drawing and the SurfaceHolder, but that's the idea.

Good luck with your game!

Upvotes: 1

Related Questions