I am developing a simple game that is running in its own thread. When I launch the app, everything works great. The thread is initialized, and the update() and render() functions are executed from the thread, which is great.
If I exit the app, the thread is paused. When I launch the app again, the thread resumes fine. (The update and render functions are executed). Again, this is great!
THE PROBLEM IS, I hit the power button to sleep the device. When I resume the app from this state, the render() function appears to work fine; it is drawing all of the elements (background, sprites, etc) exactly where they were before I hit the button. BUT: The sprites are no longer animated! After a bit of tracing and troubleshooting, I found out that although the "running" flag is set to TRUE, it seems like the "RUN" function is no longer running! (On an another note, I am confused why the RENDER function works, but the UPDATE function does not, unless the app is rendering the objects from some other method? Irregardless...)
I am working this problem from two possible angles; I am either missing something in the way Android manipulates my thread, OR would this have something to do with the fact that the member variables inside the objects have been reset...
Can someone please review the basic code I have posted below, and advise any suggestions on how to improve thread manipulation? I am an intelligent person, and I have read so many articles from different sources on the subject, and am more confused now more than ever! Am I on the right track here, or is there a more intelligent way of doing this? Do I really need a separate game thread, or can I run it using the device's main thread instead?
Thanks for your feedback!
public class GameThread extends Thread {
private boolean running=false;
private SurfaceHolder surfaceHolder;
private GameView gameView;
public GameThread(SurfaceHolder surfaceHolder, GameView gameView){
super();
this.surfaceHolder = surfaceHolder;
this.gameView = gameView;
}
#Override
public void run(){
Canvas canvas;
// THIS ONLY RUNS ONCE!
// WHEN I TURN THE POWER OFF AND BACK ON, THE RUN() METHOD DOES NOT RUN!
// ODDLY ENOUGH, IF I WERE TO EXIT THE APP, AND GO BACK IN, IT WORKS FINE!
// THE running FLAG SEEMS TO CHANGE PROPERLY WHEN PAUSING OR RESUMING (SEE gameView, BELOW).
while (running){
canvas = null;
try {
canvas = this.surfaceHolder.lockCanvas();
synchronized (surfaceHolder){
gameView.update();
gameView.render(canvas);
}
} finally {
if (canvas!=null)
surfaceHolder.unlockCanvasAndPost(canvas);
}
}
}
public void setRunning(boolean running){
this.running = running;
}
}
public class GameView extends SurfaceView implements SurfaceHolder.Callback {
private GameThread thread;
private GameScreen gameScreen;
public GameView(Context context) {
super(context);
gameScreen = new GameScreen(context);
setFocusable(true);
thread = new GameThread(getHolder(), this);
getHolder().addCallback(this);
}
#Override
public void surfaceCreated(SurfaceHolder holder) {
thread = new GameThread(getHolder(), this);
thread.setRunning(true);
thread.start();
}
#Override
public void surfaceDestroyed(SurfaceHolder holder) {
boolean retry = true;
while (retry){
try {
thread.join();
retry = false;
} catch (InterruptedException e){
// Do nothing; continue trying
}
}
}
public void render(Canvas canvas){
gameScreen.draw(canvas);
}
public void update(){
gameScreen.update();
}
#Override
public boolean onTouchEvent(MotionEvent event){
gameScreen.touch(event);
}
public void resume(){
// TODO: Resume events go here
thread.setRunning(true);
}
public void pause(){
// TODO: Pause events go here
thread.setRunning(false);
}
}
public class GameScreen {
public GameScreen(Context context){
// Initialize private variables here
}
public void draw(Canvas canvas){
// Draw events go here
}
public void update(){
// Update events go here
}
public void touch(MotionEvent event){
// Touch events go here
}
}
The update() method needs to be executed BEFORE trying to post the canvas. Still unsure why this is, but it works as intended now.
Canvas canvas = null;
while (running){
gameView.update();
try {
canvas = this.surfaceHolder.lockCanvas();
synchronized (surfaceHolder){
gameView.render(canvas);
}
} finally {
if (canvas!=null)
surfaceHolder.unlockCanvasAndPost(canvas);
}
}
}
Related
I am trying to make a small 2D java game by following this YouTube Guide.
I am not following it 100% but sort of along those lines. When I try to run my program, it opens infinitely and does not stop opening. I have turned it into a thread(??) and added starts and stops but it does not seem to work. Any ideas?
public class game implements Runnable {
private Thread thread;
private boolean running = false;
private BufferStrategy bs;
private Graphics g;
public game(){
}
private void init(){
new frame();
}
private void update(){
}
private void render(){
frame frame = new frame();
bs = frame.getCanvas().getBufferStrategy();
if (bs == null){
frame.getCanvas().createBufferStrategy(3);
return;
}
g = bs.getDrawGraphics();
}
public void run() {
init();
while(running){
update();
render();
}
stop();
}
public synchronized void start(){
if(running)
return;
running = true;
thread = new Thread(this);
thread.start();
}
public synchronized void stop(){
if(!running)
return;
running = false;
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
It runs off a Launcher class like so.
public static void main(String[] args) {
game game = new game();
game.start();
}
I can ive the full code if need be. I have tried asking the creator and looking at his source code but everything seems to be identical (apart from the display deviations I have made to alter my game from his.)
TL:DR Game opens infinite frames and eventually crashes.
Had to make getCanvas() method static and it worked. Simple.
I know that there are a lot of questions about this topic, however, I am still not completely satisfied with the answers provided.
The situation:
I implemented SurfaceView with drawing in another thread using SurfaceHolder like it is suggested in the developer's guide: http://developer.android.com/guide/topics/graphics/2d-graphics.html
The problem:
Sometimes I get java.lang.IllegalStateException: Surface has already been released:
when calling SurfaceHolder.lockCanvas()
or when calling SufraceHolder.unlockCanvasAndPost()
This means that my surface is sometimes released before I lock the canvas, and sometimes - between locking and unlocking it.
My solution:
I perform surface check and locking/unlocking canvas within synchronized blocks, so I know for sure that the surface can't get destroyed in between those actions. However, I have never worked with synchronized blocks and I wanted to ask if there is anything wrong with it. So far the code worked fine, but you never know when synchronization issues may show themselves, so I'm not completely convinced that this is the best way.
private class DrawingThread extends Thread{
#Override
public void run() {
super.run();
while (!isInterrupted() && holder != null) {
Canvas drawingCanvas = null;
synchronized (this) {
if (holder.getSurface().isValid()) {
drawingCanvas = holder.lockCanvas();
}
}
if (drawingCanvas != null && drawingCanvas.getWidth() > 0) {
drawThisView(drawingCanvas);
synchronized (this) {
if(holder.getSurface().isValid()) {
holder.unlockCanvasAndPost(drawingCanvas);
}
}
}
}
}
}
#Override
public void surfaceCreated(SurfaceHolder holder) {
if(drawingThread != null){
drawingThread.interrupt();
}
drawingThread = new DrawingThread();
drawingThread.start();
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
if(drawingThread.isInterrupted()){
drawingThread = new DrawingThread();
drawingThread.start();
}
}
#Override
public void surfaceDestroyed(SurfaceHolder holder) {
drawingThread.interrupt();
}
The synchronized statement is used for mutually-exclusive access between multiple threads. If you synchronized (this) in thread #1, then any other thread attempting to do the same will block until thread #1 exits the synchronized block.
If you only ever use it in one thread, it does nothing useful. Of course, if you have multiple threads attempting to lock and unlock the Surface's Canvas, you should address the problem by not doing that in the first place, rather than trying to enforce exclusive access after the fact.
Your use of interrupt() and isThreadInterrupted() doesn't make a lot of sense. If you want to raise a flag that tells a thread that it's time to stop running, a volatile boolean works just fine. The only advantage of interrupt() is that it will wake the thread if it's waiting for an object to signal. Further, consider the docs for the isInterrupted() call:
Returns a boolean indicating whether the receiver has a pending interrupt request (true) or not ( false)
(Emphasis mine.) Your code allows for situations like, "if the thread is alive, but an interrupt has been raised for it, go ahead and create a new thread while the old one is still running".
Some information about working with SurfaceView can be found in this appendix. That links to an example in Grafika that starts and stops the thread based on the Surface callbacks, which you can find here.
After some hours of trials and errors I think I have figured it out. After all, all I had to do is to read the android reference. Here are some important things that I got wrong:
SurfaceHolder.getSurface.isValid() only checks if the surface can
be locked, so this check can (and does) fail for seeing is the canvas
can be unlocked.
To check if you can call unlockCanvasAndPost(),
you just check if the canvas returned by lockCanvas() is not
null.
If the canvas is null, you should not unlock it (if you
try it will throw an exception).
If it's not null, you have to unlock it, otherwise your app will freeze if you activity tries to stop (after onPause() the SurfaceHolder tries to destroy the Surface, but since the Canvas is locked, it couldn't, so you get into a deadlock).
Hope this can help other "noobs". Finally, this is my final code for SurfaceView(the try/catch block is not necessary any more):
DrawingThread drawingThread;
private class DrawingThread extends Thread{
public volatile boolean canDraw = true;
#Override
public void run() {
try {
while (canDraw) {
Canvas drawingCanvas = null;
if (canDraw && holder.getSurface().isValid()) {
drawingCanvas = holder.lockCanvas();
if (drawingCanvas != null) {
drawThisView(drawingCanvas);
holder.unlockCanvasAndPost(drawingCanvas);
}
}
}
}catch(IllegalStateException e){
e.printStackTrace();
canDraw = false;
}
}
}
#Override
public void surfaceCreated(SurfaceHolder holder) {
if(drawingThread != null){
drawingThread.canDraw = false;
}
drawingThread = new DrawingThread();
drawingThread.start();
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
if(!drawingThread.canDraw){
drawingThread = new DrawingThread();
drawingThread.start();
}
}
#Override
public void surfaceDestroyed(SurfaceHolder holder) {
if(drawingThread != null) {
drawingThread.canDraw = false;
}
}
And thanks fadden for clarifying some points about drawing in another thread.
I had same issue - drawing in SurfaceView using background thread and, sometimes, application hanged up when activity closed. Tracing revealed that surface in this case was destroyed between lockCanvas and unlockCanvasAndPost.
It seems the cause of such behavior was that I misinterpret docs a bit. Callback.surfaceDestroyed has to be used to hold surface from destroying (by not returning from it) until background thread finishes with surface.
DrawingThread drawingThread;
ReentrantLock paintLock = new ReentrantLock();
private class DrawingThread extends Thread{
public volatile boolean canDraw = true;
#Override
public void run() {
try {
while (canDraw) {
Canvas drawingCanvas = null;
paintLock.lock();
if (canDraw)) {
drawingCanvas = holder.lockCanvas();
if (drawingCanvas != null && drawingCanvas.getWidth() > 0) {
drawThisView(drawingCanvas);
holder.unlockCanvasAndPost(drawingCanvas);
}
}
paintLock.unlock();
}
}catch(IllegalStateException e){
e.printStackTrace();
canDraw = false;
}
}
}
...
#Override
public void surfaceDestroyed(SurfaceHolder holder) {
paintLock.lock();
if(drawingThread != null) {
drawingThread.canDraw = false;
}
paintLock.unlock();
}
Placing Lock, Draw and Unlock within a single synchronised statement fixed the Exception.
I came to know about the synchronised statement after reading this thread, correct me if this is a bad idea.
private class DrawingThread extends Thread{
#Override
public void run() {
super.run();
while (!isInterrupted() && holder != null) {
Canvas drawingCanvas = null;
synchronized (this) {
if (holder.getSurface().isValid()) {
drawingCanvas = holder.lockCanvas();
}
if (drawingCanvas != null && drawingCanvas.getWidth() > 0) {
drawThisView(drawingCanvas);
}
if(holder.getSurface().isValid()) {
holder.unlockCanvasAndPost(drawingCanvas);
}
} // Lock -> Draw -> Unlock in a single synchronised statement
}
}
}
I have an activity which calls a class that extends a SurfaceView and implement Runnable
and sets the contentView() of the activity class to the instance of the surfaceview class. After minimizing the activity, i pause and destroy the thread:
public void pause(){
running = false;
while(true){
try{
renderThread.join();
break;
}catch(InterruptedException e){
//retry
}
}
}
when the activity resumes i recreate the thread:
public void resume(){
running = true;
renderThread = new Thread(this);
renderThread.start();
}
note that those are called within the onPause() and onResume() activity methods.
public void run(){
while(running){//thred loop
if(!holder.getSurface().isValid())
continue;
if(puzzleDrawn!=true) {
canvas = holder.lockCanvas();
drawPuzzle(canvas);
holder.unlockCanvasAndPost(canvas);
}
}
}
public void drawPuzzle(canvas){
//draws on canvas
}
when i try to reopen the application i see a black screen.
I need the drawPuzzle(canvas) method to be drawn just once.
any tips?
if u need more info let me know!
Thanks
In your pause method you are calling break in the wrong spot. What you should do is call break at the end of the while loop after the try and catch brackets and set your thread to null again outside the scope of the loop and just before you end your pause method, like this:
public void pause(){
running = false;
while(true){
try{
renderThread.join();
}catch(InterruptedException e){
//retry
}
break;
}
renderThread = null;
}
I am moving an image and I want to play a sound file after the object's animation is completed
The image moves but I tried using Threads to wait until a duration but it didn't work.
Animation animationFalling = AnimationUtils.loadAnimation(this, R.anim.falling);
iv.startAnimation(animationFalling);
MediaPlayer mp_file = MediaPlayer.create(this, R.raw.s1);
duration = animationFalling.getDuration();
mp_file.pause();
new Thread(new Runnable() {
public void run() {
try {
Thread.sleep(duration);
mp_file.start();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
Thanks.
you can register a delegate for the animation:
animationFalling.setAnimationListener(new AnimationListener() {
#Override
public void onAnimationStart(Animation animation) {
}
#Override
public void onAnimationRepeat(Animation animation) {
}
#Override
public void onAnimationEnd(Animation animation) {
// here you can play your sound
}
);
you can read more about the AnimationListener here
Suggest you
Create an object to encapsulate the "animation" lifetime
In the object, you'll have a thread OR a Timer
Provide methods to start() the animation and awaitCompletion()
Use a private final Object completionMonitor field to track completion, synchronize on it, and use wait() and notifyAll() to
coordinate the awaitCompletion()
Code snippet:
final class Animation {
final Thread animator;
public Animation()
{
animator = new Thread(new Runnable() {
// logic to make animation happen
});
}
public void startAnimation()
{
animator.start();
}
public void awaitCompletion() throws InterruptedException
{
animator.join();
}
}
You could also use a ThreadPoolExecutor with a single thread or ScheduledThreadPoolExecutor, and capture each frame of the animation as a Callable. Submitting the sequence of Callables and using invokeAll() or a CompletionService to block your interested thread until the animation is complete.
I want to pass my renderer some values from another class. After the renderer has calculated the values, I have a mutex in a helper class that should tell me that the renderer has finished calculating so I can continue with these new values. I can pass the renderer the values without problems, but I can't figure out how to get them back. I currently use some static variables, but after they are changed by the renderer, they seem to get lost. They aren't visible in my other class.
Example:
A class
public class View extends SurfaceView{
private void doSomething(){
glSurfaceView.queueEvent(new Runnable() {
#Override
public void run() {
//..
renderer.calculate(stack);
}
});
}
private void doAnotherThing(){
//Never happens:
if(Helper.hasCalculated){
/...
}
}
}
In my renderer:
public class MyRenderer implements GLSurfaceView.Renderer{
private void calculate(Stack stack){
Helper.hasCalculated = true
}
}
My helper class:
public class Helper{
public static volatile boolean hasCalculated = false;
}
hasCalculated is definitely set to true in the renderer, but my other class always sees it as false. Any idea why? My best guess is that it's because its in another thread, but how would I solve that? If there is a cleaner and safer approach, I'd be happy to hear him.
You can keep hold of your renderer as a variable in your activity (don't just do mGLView.setRenderer(new MyRenderer()); as a lot of people do, but rather MyRenderer myRenderer = new MyRenderer(); mGLView.setRenderer(myRenderer);). Then you can communicate with your renderer easily through method calls. The problem then just comes down to cross-thread communication. I've put two examples below, one with communication between a non-UI thread, the GL thread and the main UI thread. The second example is just for communication between the GL thread and UI thread
public class Test3D extends Activity{
private MyRenderer renderer; // keep hold of the renderer as a variable in activity
private MyAsyncTask gameLoop;
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
myRenderer = new MyRenderer(); // create the renderer object
GLSurfaceView mGLView = (GLSurfaceView)findViewById(R.id.glsurfaceview1);
mGLView.setEGLConfigChooser(true);
mGLView.setRenderer(myRenderer); // set the surfaceView to use the renderer
gameLoop = new MyAsyncTask();
gameLoop.execute(); // start a new, non-UI, thread to do something
}
/// non-UI thread (inner class of my Test3D activity)
class MyAsyncTask extends AsyncTask<Void, Void, Void>{
#Override
protected Void doInBackground(Void... arg0) {
myRenderer.startCalc(); // tell renderer to start calculation
while(!myRenderer.isFinishedCalc()){
// waiting for calc to finish, but not blocking UI thread
try {
long x = 1000;
Thread.sleep(x);
// sleep the thread for x amount of time to save cpu cycles
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
publishProgress(null);
// when calculation has finished, we will drop out of the loop
// and update the UI
}
protected void onProgressUpdate(Void... progress) {
// update UI
}
}
}
Then in renderer
public class MyRenderer implements Renderer{
private boolean startCalc = false;
private boolean finishCalc = false;
public void startCalc(){
finishCalc = false;
startCalc = true;
}
public boolean isFinishedCalc(){
return finishCalc;
}
public void onDraw(GL10 gl){
if(startCalc){
// do calculation using GL handle
// always performed in the GL thread
finishCalc = true;
startCalc = false;
}
// draw
}
}
I've used flags in the renderer example above, but it would be fairly simple to turn that into a queue, if, say, you wanted to tell the renderer "load this array of models". Since you have to load the models (or at least textures) in the GL thread using the GL handle, you can have other classes and threads do your logic and have just the GL stuff done in the GL thread
Alternatively, if you just want to update the UI thread after your calculation is done, rather than interact with any other threads:
public class MyRenderer implements Renderer{
private Handler handler = null;
public static final int CALC_FINISHED = 1;
public void startCalc(Handler handler){
this.handler = handler;
}
public void onDraw(GL10 gl){
if(handler!=null){
// do calculation using GL handle
int flag = MyRenderer.CALC_FINISHED;
handler.dispatchMessage(Message.obtain(handler, flag));
// adds a message to the UI thread's message queue
handler = null;
}
// draw
}
}
and then from anywhere:
myRenderer.startCalc(new Handler(){
public void handleMessage (Message msg){
if(msg.what==MyRenderer.CALC_FINISHED){
// Update UI
// this code will always be executed in the UI thread
}
}
});