End SurfaceView and GameThread on exiting app - android

I've got a surfaceview and a gameThread class.
The gameThread keeps updating and painting on the SurfaceView class.
Now when I exit the app (By pressing the home or back button) I get a message that the app force closed. That's because the GameThread still tries to draw on the erased surfaceview...
So how do i properly end the app without getting this force close notification? I would like the GameThread class to stop when the back button is pressed. It should pause when pressing on home and running in the background. When re-entering the still running game it should resume....
Any ideas?
This is my GameThread class:
public class GameThread extends Thread{
private GameView view;
public boolean isRunning = false;
public GameThread(GameView view) {
this.view = view;
}
public void setRunning(boolean setRunning) {
isRunning = setRunning;
}
public void run() {
while(isRunning) {
Canvas c = null;
view.update();
try {
c = view.getHolder().lockCanvas();
synchronized (view.getHolder()) {
view.draw(c);
}
}finally {
if(c != null) {
view.getHolder().unlockCanvasAndPost(c);
}
}
}
}
It keeps updating my GameView class:
public class GameView extends SurfaceView{
private GameThread gameThread;
public GameView(Context context, Activity activity) {
super(context);
gameThread = new GameThread(this);
init();
holder = getHolder();
holder.addCallback(new SurfaceHolder.Callback() {
#Override
public void surfaceDestroyed(SurfaceHolder holder) {
}
#Override
public void surfaceCreated(SurfaceHolder holder) {
gameThread.setRunning(true);
gameThread.start();
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width,
int height) {
}
});
}
public void init() {
}
public void update() {
}
public void draw(Canvas canvas) {
super.draw(canvas);
}
}
When pressing home my logcat shows this up:
02-24 18:24:59.336: E/SurfaceHolder(839): Exception locking surface
02-24 18:24:59.336: E/SurfaceHolder(839): java.lang.IllegalStateException: Surface has already been released.
02-24 18:24:59.336: E/SurfaceHolder(839): at android.view.Surface.checkNotReleasedLocked(Surface.java:437)
02-24 18:24:59.336: E/SurfaceHolder(839): at android.view.Surface.lockCanvas(Surface.java:245)
02-24 18:24:59.336: E/SurfaceHolder(839): at android.view.SurfaceView$4.internalLockCanvas(SurfaceView.java:872)
Main activity:
public class MainActivity extends Activity {
private RelativeLayout relativeLayout;
private GameView gameView;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
gameView = new GameView(this, this);
setContentView(R.layout.activity_main);
relativeLayout = (RelativeLayout)findViewById(R.id.mainView);
relativeLayout.addView(gameView);
}
#Override
public void onBackPressed() {
// TODO Auto-generated method stub
super.onBackPressed();
gameView.onBackPressed();
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
}

Well you can make changes to your code like this.
In GameView
#Override
public void surfaceCreated(SurfaceHolder holder) {
MainActivity obj=new MainActivity();
stop=obj.getBoolean(); //receive status of boolean from main activity
//stop is boolean set if backPressed in main activity
if(!stop){
gameThread.setRunning(true);
gameThread.start();
}
else
gameThread.setRunning(false);
}
In MainActivity
public Boolean getBoolean(){
return stop;
}
public void setBoolean(Boolean bools){
stop=bools;
}
#Override
public void onBackPressed() {
// TODO Auto-generated method stub
super.onBackPressed();
stop=true;
setBoolean(stop);
try{
Thread.sleep(200); // Be safeside and wait for Game thread to finish
}
catch(Exception e){
e.printStackTrace();
}
MainActivity.finish();
}
Call setBoolean once before backPress else it will give error. Make amendments to code as per your needs.
Hope it helps. Cheers. :)

The Surface underlying the SurfaceView gets destroyed between onPause() and onDestroy() in the app lifecycle. Send a message to your renderer thread in onPause(), and wait for the thread to shut down.
For an example, see the "Hardware scaler exerciser" activity in Grafika. It starts the render thread in the SurfaceHolder's surfaceCreated() callback.
Update: it felt weird to start the thread in surfaceCreated() and stop it in onPause(), so after a bit of reshuffling HardwareScalerActivity now stops the thread in surfaceDestroyed(). (The SurfaceView documentation is a bit ambiguous -- it says the Surface is valid "between surfaceCreated() and surfaceDestroyed()" -- but looking at the SurfaceView implementation this appears to be the expected usage.)
Update 2: I updated the code some more after finding cases it didn't handle well. There's now a 60-line megacomment in the sources, which can also been seen in my answer to a similar question.
Update 3: The mega-comment turned into an architecture doc appendix. Grafika's "hardware scaler exerciser" activity demonstrates approach #2, while "texture from camera" demonstrates approach #1.

Related

surfaceCreated() called after reopening app

I'm currently working on getting the onStop() and onResume() methods right. The problem that I have is that my surfaceCreated() methode gets called everytime when I reopen the app through the onResume() methode. Because the game gets initialized in the surfaceCreated() method, which obviously causes the game to restart. But my goal is to make that it's just continues where it stopped.
I tried to stop the initializing process with the boolean started, but this causes the app to crash even it got initialized at the start.
public class GamePanel extends SurfaceView implements SurfaceHolder.Callback{
public MainThread thread;
private static MainActivity context;
private boolean started = false;
public GamePanel(MainActivity context){
super(context);
this.context = context;
getHolder().addCallback(this);
thread = new MainThread(this.getHolder(), this);
setFocusable(true);
}
#Override
public void surfaceCreated(SurfaceHolder holder) {
if(!started){
DisplayMetrics displayMetrics = new DisplayMetrics();
context.getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
thread.start();
}
thread.setRunning(true);
}
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
#Override
public void surfaceDestroyed(SurfaceHolder holder) {
try {
thread.setRunning(false);
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
I removed all the unimportant stuff
public class MainActivity extends AppCompatActivity {
private GamePanel gamePanel;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
this.requestWindowFeature(Window.FEATURE_NO_TITLE);
gamePanel = new GamePanel(this);
setContentView(gamePanel);
}
protected void onResume(){
super.onResume();
gamePanel.thread.setRunning(true);
}
protected void onStop(){
super.onStop();
}
protected void onPause(){
super.onPause();
gamePanel.thread.setRunning(false);
}
}
Im stopping the thread by setting thread.running to false. Which should pause the app logic and only continues as soon the onResume() method is called by setting thread.running to true again.
Do i have to recreate the SurfaceView everytime i reopen the app?
Im new to android and i couldn't find the solution on the web.
Thanks in advance!

Update programmatic Layout from a SurfaceView inside the Layout

So, I have searched for an answer. Most results show how to update from outside the Layout. Here is my simplified code.
MainActivity:
public class MainActivity extends Activity{
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState)
{ this.requestWindowFeature(Window.FEATURE_NO_TITLE);
this.getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
LinearLayout surface = (LinearLayout)findViewById(R.id.surface);
surface.addView(new UnitActivity(this));}}
UnitActivity:
public class UnitActivity extends SurfaceView
{
private MainThread thread;
private SurfaceHolder holder;
public UnitActivity(Context context){
super(context);touch = new MyTouchListener();
this.setOnTouchListener(touch);
thread = new MainThread(this);
holder=getHolder();
holder.addCallback(new SurfaceHolder.Callback(){
#Override
public void surfaceCreated(SurfaceHolder p1)
{
thread.setRunning(true);
thread.start();
}
#Override
public void surfaceChanged(SurfaceHolder p1, int p2, int p3, int p4)
{
// TODO: Implement this method
}
#Override
public void surfaceDestroyed(SurfaceHolder p1)
{
boolean retry = true;
while (retry)
{
try
{
thread.join();
retry = false;
}
catch(Exception e){
}
}
}
});
setFocusable(true)
}
#Override
public void onDraw(Canvas c) {
//draw to canvas here
*******Here is where I want to, in some way
make a call to add a ScrollView or
TextView to the original Layout******
}
}
Basically, my SurfaceView is updating running a Thread. I want a certain condition to trigger a TextView or ScrollView to become visible on screen. I thought maybe I could just get the Layout to run a thread and update itself, but killed my app doing so... I appreciate any help or comments.

pause game thread when home button is pressed, re-entering resumes game thread

I have got a problem
When hitting the home button, my game thread class gets paused. But when re-entering the app and the onResume() method gets called, it should resume the game thread... everythin i get is a force close.
Here is my MainActivity:
public class MainActivity extends Activity {
private RelativeLayout relativeLayout;
private GameView gameView;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
gameView = new GameView(this, this);
setContentView(R.layout.activity_main);
relativeLayout = (RelativeLayout)findViewById(R.id.mainView);
relativeLayout.addView(gameView);
}
#Override
protected void onPause() {
super.onPause();
gameView.pause();
}
#Override
protected void onResume() {
super.onResume();
if(gameView.getGameThread() != null) {
if(gameView.getGameThread().isRunning == false) {
gameView.resume();
}
}
}
}
gameView is following class, a surfaceView getting rendered by GameThread:
public class GameView extends SurfaceView{
private GameThread gameThread;
public GameView(Context context, Activity activity) {
super(context);
gameThread = new GameThread(this);
holder = getHolder();
holder.addCallback(new SurfaceHolder.Callback() {
#Override
public void surfaceDestroyed(SurfaceHolder holder) {
}
#Override
public void surfaceCreated(SurfaceHolder holder) {
gameThread.setRunning(true);
gameThread.start();
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width,
int height) {
}
});
}
public GameThread getGameThread() {
return gameThread;
}
public void update() {
}
public void pause() {
gameThread.pause();
}
public void resume() {
gameThread.resumeThread();
}
}
This is the GameThread class:
public class GameThread extends Thread{
private GameView view;
public boolean isRunning = false;
public GameThread(GameView view) {
this.view = view;
}
public void setRunning(boolean setRunning) {
isRunning = setRunning;
}
public void stopThread() {
isRunning = false;
}
public void run() {
while(isRunning) {
Canvas c = null;
view.update();
try {
c = view.getHolder().lockCanvas();
synchronized (view.getHolder()) {
view.draw(c);
}
}finally {
if(c != null) {
view.getHolder().unlockCanvasAndPost(c);
}
}
}
}
public void startThread() {
isRunning = true;
}
public void pause() {
isRunning = false;
}
public void resumeThread() {
isRunning = true;
}
}
I removed the irrelevant parts of the code.
When re-entering the app the android.view.Choreographer class opens
Any ideas?
Actually you're not pausing your thread (to be strict, there's no such thing as pausing a thread). You're ending your thread.
When you call pause() you set the isRunning flag to false, that makes the thread exit the loop and the thread ends. When you set your 'isRunning' to true again, the thread won't restart.
Actually you can't reuse threads in java, you have to create a new one, because once the thread loop ends, it's dead.

Android application crashes when re-entering after the home button is pressed. This is the code

I've searched over and over in this forum & google about this issue, maybe it has to do something with my thread or the way how the game is starting.
This is the skeleton of the game application, please tell me what I'm doing wrong.
This is what I'm doing in thread;
public class GameThread extends Thread
{
static final long FPS = 30;
private GameView view;
private boolean running = false;
private SurfaceHolder surfaceHolder;
public boolean isSurfaceCreated = false;
private Object mPauseLock;
private boolean mPaused;
public GameThread(SurfaceHolder surfaceHolder, Handler handler, GameView view)
{
super();
mPauseLock = new Object();
mPaused = false;
this.surfaceHolder = surfaceHolder;
this.view = view;
}
public void setRunning(boolean run)
{
running = run;
}
/**
* pause thread.
*/
public void onPause() {
synchronized (mPauseLock) {
mPaused = true;
}
}
/**
* resume thread.
*/
public void onResume() {
synchronized (mPauseLock) {
mPaused = false;
mPauseLock.notifyAll();
}
}
#Override
public void run()
{
// run our thread on high priority to keep from getting slowdown
android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_URGENT_DISPLAY);
// wait for surface to become available # start
while (!isSurfaceCreated && running)
{
try {
sleep(50);
} catch (InterruptedException e) {}
}
long ticksPS = 1000 / FPS;
long startTime;
long sleepTime;
while (running){
Canvas c = null;
startTime = System.currentTimeMillis();
//long msSinceLastTick = System.currentTimeMillis() - view.lastTickMs;
try {
c = surfaceHolder.lockCanvas(null);
synchronized (surfaceHolder)
{
/*
* stop updating when pause is pressed
*/
if(mPaused == false)
{
view.update();
}
view.onDraw(c);
}
} finally {
if (c != null)
{
surfaceHolder.unlockCanvasAndPost(c);
}
}
sleepTime = ticksPS - (System.currentTimeMillis() - startTime);
try {
if (sleepTime > 0)
sleep(sleepTime);
else
sleep(10);
} catch (Exception e) {}
}
}
This is the start activity code;
import android.app.Activity;
import android.content.pm.ActivityInfo;
import android.os.Bundle;
public class test_game extends Activity {
/** Called when the activity is first created. */
private GameView Game;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Game = new GameView(this);
setContentView(Game);
}
#Override
public void onBackPressed()
{
super.onBackPressed();
//KILL ALL
finish();
System.runFinalizersOnExit(true);
System.exit(0);
}
// ===========================================================
// onPause
// ===========================================================
protected void onPause() {
super.onPause();
}
// ===========================================================
// onStop
// ===========================================================
protected void onStop() {
super.onStop();
}
// ===========================================================
// onDestroy
// ===========================================================
#Override
protected void onDestroy() {
super.onDestroy();
}
}
Added extra lines in AndroidManifest.xml
android:launchMode="singleTask"
android:alwaysRetainTaskState="true"
GameView looks like this:
public class GameView extends SurfaceView {
private SurfaceHolder holder;
private GameThread Thread = null;
private int testVar = 0;
private boolean isPaused = false;
public GameView(Context context)
{
super(context);
loadGameFiles();
holder = getHolder();
Thread = new GameThread(holder, new Handler(), this);
holder.addCallback(new SurfaceHolder.Callback()
{
#Override
public void surfaceDestroyed(SurfaceHolder holder)
{
boolean retry = true;
Thread.setRunning(false);
while (retry)
{
try {
Thread.join();
retry = false;
} catch (InterruptedException e)
{
}
}
}
#Override
public void surfaceCreated(SurfaceHolder holder)
{
Thread.isSurfaceCreated = true;
if(isPaused == true)
{
onResume();
}
Thread.setRunning(true);
Thread.start();
GameMenu();
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height)
{
}
});
setFocusable(true);
}
public boolean isPaused()
{
return this.isPaused;
}
/**
* Pauses the physics update & animation.
*/
public void onPause() {
synchronized (holder)
{
if(isPaused == false)
{
isPaused = true;
Thread.onPause();
}
}
}
public void onResume()
{
synchronized (holder)
{
if(isPaused == true)
{
isPaused = false;
Thread.onResume();
}
}
}
etc..
Thanks for the replies, the forum commentbox doesn't let me post this; anyway, here it is:
Hi Frankenstein, I've tried what you suggested here, still doesn't work. My resume code in the GameView looks like this;
public void onResume(){ synchronized (holder)
{ if(isPaused == true){
isPaused = false; //resume notification to the gameview
Thread.onResume(); //resume the thread
}
}
}
This is on the Activity class;
#Override
protected void onPause() {
super.onPause();
Game.onPause();
}
#Override
protected void onResume() {
super.onResume();
Game.onResume();
}
I have not used logcat, not sure how to use it, I can sure call it in each steps in java but where does it displays the error/logs? thanks in advance guys!
When I resume it shows following error in LogCat;
Added this line to avoid the previous problem;
Thread.isSurfaceCreated = true;
Thread.setRunning(true);
Thread.start();
loadGame();
UPDATE:
The application returns to black screen, but it's not frozen.
Errors (updated) in logcat;
12-14 16:54:52.176: ERROR/MediaPlayerService(3937): create PVPlayer
12-14 16:55:03.172: ERROR/dalvikvm(4619): Failed to write stack traces to /data/anr/traces.txt (1093 of 3526): Unknown error: 0
12-14 16:55:03.910: ERROR/dalvikvm(25464): Failed to write stack traces to /data/anr/traces.txt (2401 of 3332): Math result not representable
12-14 16:55:03.918: ERROR/dalvikvm(25279): Failed to write stack traces to /data/anr/traces.txt (-1 of 2354): Math result not representable
12-14 16:55:32.394: ERROR/imdg81(19379): IsShutDownStarted()
12-14 16:55:32.590: ERROR/imdg81(19379): IsShutDownStarted()
12-14 16:55:37.902: ERROR/MediaPlayer(19379): error (100, 0)
12-14 16:55:37.930: ERROR/SoundPool(5264): Unable to load sample: (null)
12-14 16:55:39.281: ERROR/SoundBooster(5328): readSBTable: file open error!
12-14 16:55:39.281: ERROR/AcousticEQ(5328): [AEQ] aeqcoe.txt: file open error!
12-14 16:55:39.500: ERROR/AudioService(19379): Media server died.
12-14 16:55:39.500: ERROR/AudioService(19379): Media server started.
12-14 16:55:39.996: ERROR/MediaPlayerService(5328): create PVPlayer
In your onSurfaceCreated method you have this block of code:
if(isPaused == true){
onResume();
}
Thread.setRunning(true);
Thread.start();
your call to Thread.start() is called no matter what (even if isPaused is true). Since your thread is already started in this case, you're trying to start it twice and thus getting an illegal state exception.
Thanks guys!
I've fixed this issue, the black screen was showing because the thread isn't created again but being manipulated when I entered 2nd time to the screen. Therefore, it showed an empty/null thread being manipulated as black screen. I've added following line to OnResume()
if(!Thread.isAlive())
{
Thread = new GameThread(holder, new Handler(), this);
}
This is the fix for my issue, but now I've to implement onSaveInstanceState & onRestoreInstanceState to avoid the from re-starting from beginnig.

Automatically Exit SurfaceView

I have a game I'm developing for Android 2.x. When the player looses, I want to automatically exit that and go back to the previous menu of the game. This may be a simple question, but how do I do that?
I have a MenuActivity, which calls GameActivity, which calls the GameView (implementation of SurfaceView) where the game logic is.
UPDATE: No, wait, it's more complex than that. To make this game I followed the MoonLander example, which used threads.
GameView:
public class GameActivity extends Activity
{
GameView gameView;
GameThread gThread;
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
gameView = new GameView(this);
setContentView(gameView);
gThread = gameView.getThread();
gThread.doStart();
}
}
GameView:
package com.cp.balldrop;
public class GameView extends SurfaceView implements SurfaceHolder.Callback
{
class GameThread extends Thread
{
public GameThread(SurfaceHolder sHolder, Context context, Handler handler)
{
//Code
}
public void doStart()
{
synchronized (mSurfaceHolder)
{
}
}
#Override
public void run()
{
while (running)
{
Canvas c = null;
try
{
c = mSurfaceHolder.lockCanvas(null);
synchronized (mSurfaceHolder)
{
doDraw(c);
}
}
finally
{
if (c != null)
{
mSurfaceHolder.unlockCanvasAndPost(c);
}
}
}
}
protected void doDraw(Canvas canvas)
{
//code
}
}
/** Handle to the application context, used to e.g. fetch Drawables. */
private Context mContext;
/** Pointer to the text view to display "Paused.." etc. */
private TextView mStatusText;
/** The thread that actually draws the animation */
private GameThread gThread;
public GameView(Context context)
{
super(context);
// register our interest in hearing about changes to our surface
SurfaceHolder holder = getHolder();
holder.addCallback(this);
// create thread only; it's started in surfaceCreated()
gThread = new GameThread(holder, context, new Handler()
{
#Override
public void handleMessage(Message m)
{
// mStatusText.setVisibility(m.getData().getInt("viz"));
// mStatusText.setText(m.getData().getString("text"));
}
});
setFocusable(true);
}
public GameThread getThread()
{
return gThread;
}
public void surfaceCreated(SurfaceHolder holder)
{
gThread.start();
}
#Override
public void surfaceDestroyed(SurfaceHolder holder)
{
boolean retry = true;
gThread.setRunning(false);
while (retry)
{
try
{
gThread.join();
retry = false;
}
catch (InterruptedException e)
{
}
}
}
}
In my thread, I call the following method from my surfaceview's doTouch:
public void doLose() {
synchronized (mSurfaceHolder) {
//quit to mainmenu
((Activity) mContext).finish();
}
}
This seems to shut down the surfaceview and the activity hosting it, dumping me back in my main-menu activity. Note the casting and the predefined context.
What about calling finish() on the GameActivity?

Categories

Resources