Android SurfaceView UI Thread Not Working - java

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.

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!

Related

Run method on another thread android

i have an object that implements runnable. This object is my gameview, where the world of the game is processed.
Gameview is owned by another class: Game. Game is handling the UI for the gameView, so it is running on the ui-thread.
When the user touches something it is caught by the UI-thread. This is because if the user touches perhaps a skill-icon i need to pass data about what skill it was to the gameView.
My issue is that quite get:
java.util.ConcurrentModificationException
at java.util.Vector$Itr.checkForComodification(Vector.java:1193)
at java.util.Vector$Itr.next(Vector.java:1145)
at com.example.benjamin.dungeondweller.RenderSystem.render(RenderSystem.java:55)
at com.example.benjamin.dungeondweller.GameView.run(GameView.java:90)
at java.lang.Thread.run(Thread.java:764)
When touching the screen. I suspect it is because the method in GameView is still running on the UI thread, not the thread in the gameview.
To clarify here is the method in game (that is running on the UI-thread):
Where the third argument in gameView.touch would be the skill id (currently set to -1)
#Override
public boolean onTouchEvent(MotionEvent event) {
int x = (int)event.getX();
int y = (int)event.getY();
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
gameView.touch(x,y, -1);
break;
}
return true;
}
gamview.Touch:
public void touch(int screenX, int screenY, int skillId) {
int worldX = renderSystem.screenToWorldX(screenX);
int worldY = renderSystem.screenToWorldY(screenY);
renderSystem.clear();
if (map[worldX][worldY] != 1) {
ArrayList<Point> route = calculateRoute(new Point(0,0), new Point(worldX,worldY));
for (Point p : route) {
renderSystem.addSprite(R.drawable.guy, p.x, p.y);
}
}
(Currently only removing every sprite on the screen and adds new sprites)
When the crash occurs, the log refers to the run method in gameView:
#Override
public void run() {
while (running) {
if (!surfaceHolder.getSurface().isValid()) {
continue;
}
long startTime = System.currentTimeMillis();
int frameSkipped = 0;
//update();
Canvas canvas = surfaceHolder.lockCanvas();
if (canvas != null) {
canvas.drawARGB(255,65,65,65);
renderSystem.render(canvas); <---------- CRASH HERE
surfaceHolder.unlockCanvasAndPost(canvas);
}
long updateTime = System.currentTimeMillis() - startTime;
int sleepTime = (int)(FRAME_PERIOD - updateTime);
if (sleepTime > 0) {
try {
Message msg = new Message();
msg.obj = MAX_FPS;
msg.what = UPDATE_FPS;
handler.dispatchMessage(msg);
Thread.sleep(sleepTime);
} catch (InterruptedException e) {}
}
while (sleepTime < 0 && frameSkipped < MAX_FRAME_SKIPS) {
Message msg = new Message();
msg.obj = 1000 / updateTime;
msg.what = UPDATE_FPS;
handler.dispatchMessage(msg);
// update();
sleepTime+=FRAME_PERIOD;
frameSkipped++;
}
}
}
I think the issue is that the run method and touch method in GameView is running on different threads but i cant say for certain. How do i make it so that gameView.touch is running on the same thread as gameView.run?
Edit: formated code.
Edit: So to clarify further, i have one thread that's iterating the vector and one that tries to remove items in it. How do i make it so that the thread that removes items in the vector runs on the same thread as the one that iterates over it?

How to pause program execution without blocking a Timer (Java Swing)?

I have a Timer that shows an animation on the screen when a game event takes place. What I want to do is have the main execution pause until this animation finishes, but everything I try seems to block the Timer from running. I've tried using Thread.sleep() and calling wait() and notify() on a lock object, but with the same result. The Timer listener's actionPerformed() is never called.
This code sets up the timer:
protected void showMovingEffect(int steps, Direction dir, AnimatedImageSet imgs) {
effectsPane.runAnimation(imgs, dir, steps);
waiting = true;
while (waiting) {
synchronized(animationWaitLock) {
try {
animationWaitLock.wait();
} catch (InterruptedException e) {}
}
}
}
And the Timer and listener inside the EffectsPane object:
private void runAnimation(AnimatedImageSet ais, Direction dir, int iters) {
System.out.println("run");
imgs = ais;
top = 0;
left = 0;
topStep = dir.getRowIncrement() * 10;
leftStep = dir.getColIncrement() * 10;
iterations = iters;
index = 0;
active = true;
timer.start();
}
public void actionPerformed(ActionEvent e) {
System.out.println(index);
top += topStep;
left += leftStep;
index++;
currentImage = imgs.getImage(index);
repaint();
if (index == iterations) {
active = false;
timer.stop();
synchronized(animationWaitLock) {
animationWaitLock.notify();
}
waiting = false;
}
}
The System.out.println call at the start of the actionPerformed() is never called, so either the wait() call is also pausing the Timer, or something is blocking it. If I comment out the sleep/wait lines in showMovingEffect(), the animation runs, but the program does not pause.
One approach would be to display a modal dialog while the animation proceeds. As discussed here, user interaction will be foreclosed, but background GUI updates in response to the javax.swing.Timer will continue. You can allow the user to dismiss the dialog at any time, as shown here, but you may want to abandon the animation at that point.
no input from the user is needed.
You can block user interaction without displaying a dialog by entering a SecondaryLoop after the animation starts.
SecondaryLoop loop = Toolkit.getDefaultToolkit()
.getSystemEventQueue().createSecondaryLoop();
timer.start();
loop.enter();
When the animation concludes, exit() the SecondaryLoop:
public void actionPerformed(ActionEvent e) {
…
if (index == iterations) {
timer.stop();
loop.exit ();
…
}
}
You can try this:
timer.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
// your code
}
});

Swing animation pause and resume

I want to add possibility to pause/resume to my game. Unfortunately my solution only pauses the game, but does not resume it (when I click resume button nothing happens - the image is still). I also tried to call wait/notify methods on thread, but it also did not work - I got Exception in thread "AWT-EventQueue-0" java.lang.IllegalMonitorStateException. What is the best solution to make pause/resume?
Game Loop
public void run() {
init();
long startTime;
long reTime;
long waitTime;
while (running) {
startTime = System.nanoTime();
int CarPosX = car.zwrocPolozenieX();
int CarPosY = car.zwrocPolozenieY();
update(b, CarPosX, CarPosY);
render(b);
//draw();
repaint();
if(b<4390) {
b=b+szybkoscMapy;
}
reTime = System.nanoTime() - startTime;
waitTime = targetTime - reTime/100000000;
try {
Thread.sleep(waitTime);
}
catch(Exception e) {
}
}
}
public void init() {
if(thread == null) {
thread = new Thread(this);
thread.start();
}
b=0;
running = true;
image = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);
g = (Graphics2D) image.getGraphics();
map = new Map(levelNumber);
car = new Car(map);
car.setxpos(338);
car.setypos(150);
}
Listeners
pause_button.addActionListener( new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
bp.running = false;
}
});
resume_button.addActionListener( new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
bp.running = true;
}
});
Use javax.swing.Timer to pace the animation. Typical controls that invoke the timer's start() and stop() methods are examined here. This fleet simulation, which uses such a Timer, may also be if interest.

I have an issue with a NullPointerException upon resuming my Android app and rendering the canvas

my app currently crashes when you press the home or back button saying "Unfortunately, Rocks has crashed". I have narrowed it down to the line of code that reads:
// draws the canvas on the panel
this.gamePanel.render(canvas);
in this context:
package com.background.rocks;
import android.graphics.Canvas;
import android.util.Log;
import android.view.SurfaceHolder;
public class MainThread extends Thread {
private static final String TAG = MainThread.class.getSimpleName();
// desired fps
private final static int MAX_FPS = 50;
// maximum number of frames to be skipped
private final static int MAX_FRAME_SKIPS = 5;
// the frame period
private final static int FRAME_PERIOD = 1000 / MAX_FPS;
// Surface holder that can access the physical surface
private SurfaceHolder surfaceHolder;
// The actual view that handles inputs
// and draws to the surface
private Graphics gamePanel;
// flag to hold game state
private boolean running;
public void setRunning(boolean running) {
this.running = running;
}
public MainThread(SurfaceHolder surfaceHolder, Graphics gamePanel) {
super();
this.surfaceHolder = surfaceHolder;
this.gamePanel = gamePanel;
}
#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 for exclusive pixel editing
// in the surface
try {
canvas = this.surfaceHolder.lockCanvas();
synchronized (surfaceHolder) {
beginTime = System.currentTimeMillis();
framesSkipped = 0; // resetting the frames skipped
// update game state
this.gamePanel.update();
// render state to the screen
// draws the canvas on the panel
this.gamePanel.render(canvas);
// calculate how long did the cycle take
timeDiff = System.currentTimeMillis() - beginTime;
// calculate sleep time
sleepTime = (int) (FRAME_PERIOD - timeDiff);
if (sleepTime > 0) {
// if sleepTime > 0 we're OK
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) {
// we need to catch up
this.gamePanel.update(); // update without rendering
sleepTime += FRAME_PERIOD; // add frame period to check if in next frame
framesSkipped++;
}
}
} finally {
// in case of an exception the surface is not left in
// an inconsistent state
if (canvas != null) {
surfaceHolder.unlockCanvasAndPost(canvas);
}
} // end finally
}
}
}
But I'm new to using canvas so I don't know how to fix it. It creates a NullPointerException so I'm assuming it's to do with when resuming the app or pausing the app the canvas isn't stored properly.
Edit: Graphics class (without imports):
package com.background.rocks;
public class Graphics extends SurfaceView implements SurfaceHolder.Callback {
private static final String TAG = Graphics.class.getSimpleName();
private MainThread thread;
private Player player;
private ArrayList<Rock> rocks = new ArrayList<Rock>();
private Random random = new Random();
private CountDownTimer countdown;
public Graphics(Context context) {
super(context);
// adding the callback (this) to the surface holder to intercept events
getHolder().addCallback(this);
// create shape and load bitmap
player = new Player(BitmapFactory.decodeResource(getResources(), R.drawable.player_orange), 540, 1500);
// create the game loop thread
thread = new MainThread(getHolder(), this);
// make the GamePanel focusable so it can handle events
setFocusable(true);
timer();
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
#Override
public void surfaceCreated(SurfaceHolder holder) {
// at this point the surface is created and
// we can safely start the game loop
thread.setRunning(true);
thread.start();
}
#Override
public void surfaceDestroyed(SurfaceHolder holder) {
Log.d(TAG, "Surface is being destroyed");
// tell the thread to shut down and wait for it to finish
// this is a clean shutdown
boolean retry = true;
while (retry) {
try {
thread.join();
retry = false;
} catch (InterruptedException e) {
// try again shutting down the thread
}
}
Log.d(TAG, "Thread was shut down cleanly");
}
#Override
public boolean onTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
// delegating event handling to the shape
player.handleActionDown((int) event.getX(), (int) event.getY());
// check if in the lower part of the screen we exit
if (event.getY() > getHeight() - 50) {
thread.setRunning(false);
((Activity) getContext()).finish();
} else {
Log.d(TAG, "Coords: x=" + event.getX() + ",y=" + event.getY());
}
}
if (event.getAction() == MotionEvent.ACTION_MOVE) {
// the gestures
if (player.isTouched()) {
// the shape was picked up and is being dragged
player.setX((int) event.getX());
player.setY((int) event.getY());
}
}
if (event.getAction() == MotionEvent.ACTION_UP) {
// touch was released
if (player.isTouched()) {
player.setTouched(false);
}
}
return true;
}
**public void render(Canvas canvas) {
if (canvas != null) {
canvas.drawColor(Color.WHITE);
player.draw(canvas);
Rock[] rockArray = rocks.toArray(new Rock[0]);
for (Rock rock : rockArray) {
rock.draw(canvas);
}
}
}**
/**
* This is the game update method. It iterates through all the objects and
* calls their update method if they have one or calls specific engine's
* update method.
*/
public void update() {
Rock[] rockArray = rocks.toArray(new Rock[0]);
for (Rock rock : rocks) {
rock.update();
}
}
public void timer() {
if (countdown != null) {
countdown.cancel();
}
countdown = new CountDownTimer(30000, 800) {
public void onTick(long millisUntilFinished) {
rocks.add(new Rock(BitmapFactory.decodeResource(getResources(), R.drawable.rock), random.nextInt(1080 - 1) + 1, 0));
}
public void onFinish() {
countdown.start();
}
}.start();
}
}
Edit 2: So I managed to change my Render method in Graphics and so the app doesn't crash now when you press home or back, however when I try and resume the app I get a black screen. I'm assuming because it hasn't loaded the canvas back in, but I'm not sure how to do this, any help?
Start running the thread once the surface gets created. i.e. when you get a callback surfaceCreated(), start the thread.
Code Snippet
public void surfaceCreated(SurfaceHolder holder) {
thread.setRunning(true);
thread.start();
}
This might be too late but what I did to fix it was I put a n if statement to test whether the canvas is still their before rendering and updating. Should look like this:
If (canvas != null){
this.gamePanel.render (canvas);
this.gamePanel.update ();
}
I think I followed the same tutorial as you, you will probobly encounter another issue after this on is solved. Im still looking for an answer to that issue. Hope this helps!

Screen Sometimes Blank at Game Start

I've been working on a simple game engine. All was going pretty well, and I got the foundation finished. By that, I mean, the game sets up with user defined display settings, initializes stuff, and starts the game loop. Everything works great, except I have an issue in both Windowed mode and full screen:
In Windowed mode, sometimes the screen will appear blank, until I resize or minimize the window.
In full screen, I only get a blank screen if I use the default Display resolution. Most of the other resolutions appear just fine.
Here is some code relevant to displaying the screen:
This is performed when the use presses the play button on the display settings dialog
public void actionPerformed(ActionEvent e) {
DisplayMode dm = modes[resolutionBox.getSelectedIndex()];
boolean fs = fullscreenBox.getSelectedItem().equals ("Fullscreen");
boolean vs = vSyncCheckBox.isSelected();
game.initScreen (dm, fs, vs);
runGame (game);
f.dispose();
}
});
private final void runGame(EGame g)
{
Thread t = new Thread (g);
t.start();
}
This is the run method in the EGame class
public final void run() {
start();
isRunning = true;
gameLoop();
endGame();
}
The user of the engine will call the method setGameCanvas() in the start() method. Other things can go in the start() method, but for my test games, it just that method:
protected final void setGameCanvas(EGameCanvas c)
{
screen.remove (canvas);
canvas = c;
canvas.addKeyListener (keyboard);
screen.add (canvas);
}
And finally, here is the gameLoop:
private final void gameLoop()
{
// Final setup before starting game
canvas.setBuffer (2);
screen.addKeyListener (keyboard);
canvas.addKeyListener (keyboard);
int TARGET_FPS = screen.getTargetFps();
final long OPTIMAL_TIME = 1000000000 / TARGET_FPS;
long lastLoopTime = System.nanoTime();
long now = 0;
long lastFpsTime = 0;
long updateLength = 0;
double delta = 0;
int fps = 0;
while (isRunning)
{
now = System.nanoTime();
updateLength = now - lastLoopTime;
lastLoopTime = now;
delta = updateLength / ((double) OPTIMAL_TIME);
lastFpsTime += updateLength;
fps++;
if (lastFpsTime >= 1000000000)
{
screen.setFps (fps);
lastFpsTime = 0;
fps = 0;
}
keyboard.poll();
keyboardEvents();
update (delta);
render();
try {
Thread.sleep( (lastLoopTime-System.nanoTime() + OPTIMAL_TIME)/1000000 );
}
catch (Exception ex) {}
}
}
Now, keep in mind, neither of my issues existed, until I added the run method and ran it in a separate thread. I can about guarantee it's a threading issue.
But, I'm no expert, so any advice would be appreciated.
I believe the issue is that you're trying to modify GUI-related components outside of the event dispatch thread - that is the only thread allowed to modify the graphical interface, otherwise you might encounter issues like this.
To check this try if the following solves the issue:
try {
if (!SwingUtilities.isEventDispatchThread()) {
SwingUtilities.invokeAndWait( new Runnable() {
#Override
public void run() {
// Do rendering and other stuff here...
}
});
}
} catch (InterruptedException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
If that helps, I'd recommend taking a look on the Concurrency in Swing tutorial.

Categories