Reputation: 680
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
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