I have a very simple game SurfaceView and sometimes game does not responde to touch events for a few seconds, then it respondes to the all of those touch events at once. I have tested my game on Galaxy S3 and Nexus 4 and it works fine, it seems this problem only occurs on Galaxy S5.
Main Activity:
public class DroidzActivity extends Activity {
/** Called when the activity is first created. */
private static final String TAG = DroidzActivity.class.getSimpleName();
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// requesting to turn the title OFF
requestWindowFeature(Window.FEATURE_NO_TITLE);
// making it full screen
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
// set our MainGamePanel as the View
setContentView(new MainGamePanel(this));
Log.d(TAG, "View added");
}
#Override
protected void onDestroy() {
Log.d(TAG, "Destroying...");
super.onDestroy();
}
#Override
protected void onStop() {
Log.d(TAG, "Stopping...");
super.onStop();
}
}
MainGamePanel
public class MainGamePanel extends SurfaceView implements SurfaceHolder.Callback {
private static final String TAG = MainGamePanel.class.getSimpleName();
private MainThread thread;
public MainGamePanel(Context context) {
super(context);
// adding the callback (this) to the surface holder to intercept events
getHolder().addCallback(this);
// create the game loop thread
thread = new MainThread(getHolder(), this);
// make the GamePanel focusable so it can handle events
setFocusable(true);
}
#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.setRunning(false);
thread.join();
retry = false;
} catch (InterruptedException e) {
// try again shutting down the thread
}
}
Log.d(TAG, "Thread was shut down cleanly");
}
public void render(Canvas canvas){
if(canvas!=null)
canvas.drawColor(colorList[colorIndex]);
}
#Override
public boolean onTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
colorIndex++;
colorIndex = colorIndex % colorList.length;
}
return super.onTouchEvent(event);
}
int [] colorList = {Color.RED, Color.GREEN, Color.BLUE, Color.GRAY};
int colorIndex = 0;
}
MainThread
public class MainThread extends Thread {
private static final String TAG = MainThread.class.getSimpleName();
// Surface holder that can access the physical surface
private SurfaceHolder surfaceHolder;
// The actual view that handles inputs
// and draws to the surface
private MainGamePanel gamePanel;
// flag to hold game state
private boolean running;
public void setRunning(boolean running) {
this.running = running;
}
public MainThread(SurfaceHolder surfaceHolder, MainGamePanel gamePanel) {
super();
this.surfaceHolder = surfaceHolder;
this.gamePanel = gamePanel;
}
// 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;
#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
// update without rendering
// this.gamePanel.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
}
}
}
Here is the most simple version of the app, I have tried and I have able to recreate same problem again. It also sometimes takes 5-10 seconds to load on S5 while it loads less then 1 second on Nexus 4 and S3.
It looks like MainThread is starving the UI thread.
The code that ends up executing (with a lot of stuff removed) looks like:
canvas = this.surfaceHolder.lockCanvas();
// Do a ton of stuff
surfaceHolder.unlockCanvasAndPost(canvas);
canvas = this.surfaceHolder.lockCanvas();
// Do a ton of stuff
surfaceHolder.unlockCanvasAndPost(canvas);
canvas = this.surfaceHolder.lockCanvas();
// Do a ton of stuff
surfaceHolder.unlockCanvasAndPost(canvas);
This is supported by the android source. Note that SurfaceHolder#lock calls mSurfaceLock.lock(). This is also called in SurfaceHolder#updateWindow, which is called in a variety of other places in that file.
mSurfaceLock is a ReentrantLock, and the documentation states:
The constructor for this class accepts an optional fairness parameter.
When set true, under contention, locks favor granting access to the
longest-waiting thread. Otherwise this lock does not guarantee any
particular access order.
SurfaceView doesn't specify fairness, so it should be using the default, which can lead to exactly this starvation.
Try moving some of your work and particularly the sleeps outside the lock/unlock calls.
Related
I am using TarsosDSP to calculate pitch frequencies in real time. It uses an AudioDispatcher which implements Runnable and post the results via handlePitch method to make use of in the main thread.
I am using SurfaceView to draw this value as it updates. SurfaceView also requires another thread to draw on canvas. So I have 2 runnable objects. I couldnt manage how to update surface view via one thread while getting the pitch values from another thread (audiodispatcher).
I just want to use cent value which I get in the handlePitch() method to update my drawing over surfaceview. But my app freezes. Any idea?
In MainAcitivity.java (onCreate(...))
myView = (MySurfaceView) findViewById(R.id.myview);
int sr = 44100;//The sample rate
int bs = 2048;
AudioDispatcher d = AudioDispatcherFactory.fromDefaultMicrophone(sr,bs,0);
PitchDetectionHandler printPitch = new PitchDetectionHandler() {
#Override
public void handlePitch(final PitchDetectionResult pitchDetectionResult, AudioEvent audioEvent) {
final float p = pitchDetectionResult.getPitch();
runOnUiThread(new Runnable() {
#Override
public void run() {
if (p != -1){
float cent = (float) (1200*Math.log(p/8.176)/Math.log(2)) % 12;
System.out.println(cent);
myView.setCent(cent);
}
}
});
}
};
PitchProcessor.PitchEstimationAlgorithm algo = PitchProcessor.PitchEstimationAlgorithm.YIN; //use YIN
AudioProcessor pitchEstimator = new PitchProcessor(algo, sr,bs,printPitch);
d.addAudioProcessor(pitchEstimator);
d.run();//starts the dispatching process
AudioProcessor p = new PitchProcessor(algo, sr, bs, printPitch);
d.addAudioProcessor(p);
new Thread(d,"Audio Dispatcher").start();
In SurfaceView.java (below code is triggered from the constructor)
myThread = new MyThread(this);
surfaceHolder = getHolder();
bmpIcon = BitmapFactory.decodeResource(getResources(),
R.mipmap.ic_launcher);
iconWidth = bmpIcon.getWidth();
iconHeight = bmpIcon.getHeight();
density = getResources().getDisplayMetrics().scaledDensity;
setLabelTextSize(Math.round(DEFAULT_LABEL_TEXT_SIZE_DP * density));
surfaceHolder.addCallback(new SurfaceHolder.Callback(){
#Override
public void surfaceCreated(SurfaceHolder holder) {
myThread.setRunning(true);
myThread.start();
}
#Override
public void surfaceChanged(SurfaceHolder holder,
int format, int width, int height) {
// TODO Auto-generated method stub
}
#Override
public void surfaceDestroyed(SurfaceHolder holder) {
boolean retry = true;
myThread.setRunning(false);
while (retry) {
try {
myThread.join();
retry = false;
} catch (InterruptedException e) {
}
}
}});
protected void drawSomething(Canvas canvas) {
updateCanvas(canvas, this.cent); //draws some lines depending on the cent value
}
public void setCent(double cent) {
if (this.cent > maxCent)
this.cent = maxCent;
this.cent = cent;
}
UPDATE:
MyThread.java
public class MyThread extends Thread {
MySurfaceView myView;
private boolean running = false;
public MyThread(MySurfaceView view) {
myView = view;
}
public void setRunning(boolean run) {
running = run;
}
#Override
public void run() {
while(running){
Canvas canvas = myView.getHolder().lockCanvas();
if(canvas != null){
synchronized (myView.getHolder()) {
myView.drawSomething(canvas);
}
myView.getHolder().unlockCanvasAndPost(canvas);
}
try {
sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
If I understand your problem correctly, you have an independent source of events working on its own thread (PitchDetectionHandler) and a SurfaceView that you want to re-paint on its own thread when the event from the source comes. If this is the case, then I think the whole idea with sleep(1000) is wrong. You should track actual events and react to them rather than sleep waiting for them. And it seems that on Android the easiest solution is to use HandlerThread/Looper/Handler infrastructure like so:
Beware of the bugs in the following code; not only haven't I tried it but I even haven't compiled it.
import android.graphics.Canvas;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Message;
import android.view.SurfaceHolder;
public class SurfacePitchDrawingHelper implements Handler.Callback, SurfaceHolder.Callback2 {
private static final int MSG_DRAW = 100;
private static final int MSG_FORCE_REDRAW = 101;
private final Object _lock = new Object();
private SurfaceHolder _surfaceHolder;
private HandlerThread _drawingThread;
private Handler _handler;
private float _lastDrawnCent;
private volatile float _lastCent;
private final boolean _processOnlyLast = true;
#Override
public void surfaceCreated(SurfaceHolder holder) {
synchronized (_lock) {
_surfaceHolder = holder;
_drawingThread = new HandlerThread("SurfaceDrawingThread") {
#Override
protected void onLooperPrepared() {
super.onLooperPrepared();
}
};
_drawingThread.start();
_handler = new Handler(_drawingThread.getLooper(), this); // <-- this is where bug was
_lastDrawnCent = Float.NaN;
//postForceRedraw(); // if needed
}
}
#Override
public void surfaceDestroyed(SurfaceHolder holder) {
synchronized (_lock) {
// clean queue and kill looper
_handler.removeCallbacksAndMessages(null);
_drawingThread.getLooper().quit();
while (true) {
try {
_drawingThread.join();
break;
} catch (InterruptedException e) {
}
}
_handler = null;
_drawingThread = null;
_surfaceHolder = null;
}
}
#Override
public void surfaceRedrawNeeded(SurfaceHolder holder) {
postForceRedraw();
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
synchronized (_lock) {
_surfaceHolder = holder;
}
postForceRedraw();
}
private void postForceRedraw() {
_handler.sendEmptyMessage(MSG_FORCE_REDRAW);
}
public void postRedraw(float cent) {
if (_processOnlyLast) {
_lastCent = cent;
_handler.sendEmptyMessage(MSG_DRAW);
} else {
Message message = _handler.obtainMessage(MSG_DRAW);
message.obj = Float.valueOf(cent);
_handler.sendMessage(message);
}
}
private void doRedraw(Canvas canvas, float cent) {
// put actual painting logic here
}
#Override
public boolean handleMessage(Message msg) {
float lastCent = _processOnlyLast ? _lastCent : ((Float) msg.obj).floatValue();
boolean shouldRedraw = (MSG_FORCE_REDRAW == msg.what)
|| ((MSG_DRAW == msg.what) && (_lastDrawnCent != lastCent));
if (shouldRedraw) {
Canvas canvas = null;
synchronized (_lock) {
if (_surfaceHolder != null)
canvas =_surfaceHolder.lockCanvas();
}
if (canvas != null) {
doRedraw(canvas, lastCent);
_surfaceHolder.unlockCanvasAndPost(canvas);
_lastDrawnCent = lastCent;
}
return true;
}
return false;
}
}
And then in your activity class you do something like
private SurfaceView surfaceView;
private SurfacePitchDrawingHelper surfacePitchDrawingHelper = new SurfacePitchDrawingHelper();
...
#Override
protected void onCreate(Bundle savedInstanceState) {
...
surfaceView = (SurfaceView) findViewById(R.id.surfaceView);
surfaceView.getHolder().addCallback(surfacePitchDrawingHelper);
int sr = 44100;//The sample rate
int bs = 2048;
AudioDispatcher d = AudioDispatcherFactory.fromDefaultMicrophone(sr, bs, 0);
PitchDetectionHandler printPitch = new PitchDetectionHandler() {
#Override
public void handlePitch(final PitchDetectionResult pitchDetectionResult, AudioEvent audioEvent) {
final float p = pitchDetectionResult.getPitch();
float cent = (float) (1200 * Math.log(p / 8.176) / Math.log(2)) % 12;
System.out.println(cent);
surfacePitchDrawingHelper.postRedraw(cent);
}
};
PitchProcessor.PitchEstimationAlgorithm algo = PitchProcessor.PitchEstimationAlgorithm.YIN; //use YIN
AudioProcessor pitchEstimator = new PitchProcessor(algo, sr, bs, printPitch);
d.addAudioProcessor(pitchEstimator);
// d.run();//starts the dispatching process <-- this was another bug in the original code (see update)!
AudioProcessor p = new PitchProcessor(algo, sr, bs, printPitch);
d.addAudioProcessor(p);
new Thread(d, "Audio Dispatcher").start();
...
}
Note that SurfacePitchDrawingHelper encapsulates most of the logic related to drawing and there is no need in your subclass MySurfaceView (which I think is a bad idea anyway).
The main idea is that SurfacePitchDrawingHelper creates are dedicated HandlerThread when new Surface is created. HandlerThread + Looper + Handler provide a useful infrastructure of running (in an efficient way) an infinite loop on a separate thread that waits for incoming messages and handles them one by one. So its effective public API besides SurfaceHolder.Callback2 consists of single postRedraw method that might be used to ask the drawing thread to do another redraw and this is exactly what is used by custom PitchDetectionHandler. "Asking" is done by putting a message in the queue to be processed by the drawing thread (more specifically our custom Handler on that thread). I didn't bother with reducing real public API to "effective" one because it makes code a bit more complicated and I'm too lazy. But of course both "implements" can be moved to inner classes.
There is one important decision to be made by you: whether drawing thread should produce each inbound message (all cent values) in order the came or just the latest at the moment drawing occurs. This might become especially important in case PitchDetectionHandler produces events much faster then the "drawing thread" can update Surface. I believe that for most of the cases it is OK to handle just the last value from the PitchDetectionHandler but I left both version in the code for illustration. This distinction is currently implemented in the code by _processOnlyLast field. Most probably you should make this decision ones and just get rid of this almost-constant-field and the code in the irrelevant branches.
And of course don't forget to put your actual drawing logic inside doRedraw
Update (why Back button doesn't work)
TLDR version
The offending line is
d.run();//starts the dispatching process
Just comment it out!
Longer version
Looking at your example we can see that d is AudioDispatcher which implements Runnable and thus run method is a method to be called on a new thread. You may notice that this is important because inside this method does some IO and blocks the thread it runs on. So in your case it blocked the main UI thread. A few lines down you do
new Thread(d, "Audio Dispatcher").start();
and this seems to be a correct way to use AudioDispatcher
This can easily be seen from the stack traces that I asked in the comments for.
I have a Main Activity and in the corresponding XML Layout I have a Custom View that draws game objects (my tank and 10 enemies), a few buttons to control my tank and fire bullets and a TextView to show my score. My custom view is a GameSurfaceView java class that is a half screen game board.
Here is some of my code:
public class GameSurfaceView extends SurfaceView implements Runnable {
private static Context gContext;
public GameSurfaceView(Context context, AttributeSet attributeSet) {
super(context, attributeSet);
holder = getHolder();
holder.addCallback(new SurfaceHolder.Callback() {
resume();
gContext = context;
}
public void resume() {
isRunning = true;
gameThread = new Thread(this);
gameThread.start();
}
public void pause() {
isRunning = false;
boolean retry = true;
while (retry) {
try {
gameThread.join();
retry = false;
} catch (InterruptedException e) {
// try again shutting down the thread
}
}
}
#Override
public void run() {
while (isRunning) {
// We need to make sure that the surface is ready
if (!holder.getSurface().isValid()) {
continue;
}
long started = System.currentTimeMillis();
// update
step();
// draw
Canvas canvas = holder.lockCanvas();
if (canvas != null) {
render(canvas);
holder.unlockCanvasAndPost(canvas);
}
//detect all possible collisions
detectCollisions();
float deltaTime = (System.currentTimeMillis() - started);
int sleepTime = (int) (FRAME_PERIOD - deltaTime);
if (sleepTime > 0) {
try {
gameThread.sleep(sleepTime);
} catch (InterruptedException e) {
}
}
while (sleepTime < 0) {
step();
sleepTime += FRAME_PERIOD;
}
}
}
//Called from MainActivity
public void dispatchKey(int tDirection) {
Toast.makeText(gContext, "Hi", Toast.LENGTH_LONG).show();
gameStarted = true;
if (tDirection == FIRE)
Fire();
else if (tDirection != tank.Direction)
turnTankDirection = tDirection;
}
private void detectCollisions() {
//Collision Detection between tank and enemy
Toast.makeText(gContext, "Collision", Toast.LENGTH_LONG).show();
}
}
My questions:
1- Why the Toast in dispatchKey() runs correctly but Toast in detectCollisions() makes a force close?
2- How to update TextView in detectCollisions() method?
3- How to show a DialogAlert when a collision detected in detectCollisions() method?
My problem relates mainly to gContext variable.
Thanks.
Regarding question 1: Maybe this effects the second thread. While dispatchKey() is called from Activity, detectCollision() is invoked from the sureface-thread. Did you tried to call detectCollision() from activity?
Regarding question 3: let your activity implement a listener, which will be called if collision is detected. The same thing you could use as solution of question 1 and 2.
You should try:
MainActivity.this.runOnUiThread(new Runnable() {
public void run() {
//Make toast or manipulate TextView
}
});
I have an activity like below. I want the threads inside the activity terminate when the activity is paused or stopped. I searched and found volatile Boolean solution which didn't work for me. When i put the activity in pause or stop state, download continues, which i don't want it.
public class MyActivity extends Activity {
//some code here
private void foo(){
new Thread (new Runnable (){
#Override
public void run() {
//download something from internet
}
}).start();
}
}
i used this pattern which didn't work:
public class MyActivity extends Activity {
volatile Boolean state = true;
//some code here
private void foo(){
new Thread (new Runnable (){
#Override
public void run() {
while (state) {
//download something from internet
}
}
}).start();
}
#Override
public void onPause(){
super.onPause();
state = false;
}
#Override
public void onStop(){
super.onStop();
state = false;
}
}
Here's an example of the structure of a thread which stops when back is hit, which I made for my game (and works). One key difference is you're using a thread in your Activity, while in mine I call a View in my Activity and run the thread in the View. If I hit back, I return to my Activity, and can call the thread again by hitting 'start.' BECAUSE my thread behaves the way you want yours too, even if it's happening in View, I thought you may find it helpful.
Where my of my synchronization happens is with the touchscreen values, and making sure those are updated in the thread and calling function.
The thread is trashed totally unless you have a way of saving the state (if you need to).
Your thread will need to synchronize with other functions you want controlling/sharing values with the thread, like onPause, etc..
**you'll need to have some synchronized test value inside your while loop which can then change state to false, otherwise the thread will continue on its own, which is the point of threads.
public class GameView extends SurfaceView implements SurfaceHolder.Callback {
private final GameActivity gameActivity;
private GameThread _thread;
private boolean previouslyRunning = false;
private int width; //Screen width
private int height; //Screen height
private boolean newGame = true;
public GameView(Context context) {
super(context);
getHolder().addCallback(this);
this.gameActivity = (GameActivity) context;
_thread = new GameThread(getHolder(), this);
setFocusable(true);
setFocusableInTouchMode(true);
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
width = w;
height = h;
super.onSizeChanged(w, h, oldw, oldh);
//_thread.initialize();
}
#Override
public void surfaceCreated(SurfaceHolder holder) {
if (!previouslyRunning) {
_thread = new GameThread(getHolder(), this);
_thread.initialize();
}
_thread.setRunning(true);
_thread.start();
previouslyRunning = true;
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
//TODO - this was an Auto-generated method stub...
//TODO - research what this might be useful for
}
#Override
public void surfaceDestroyed(SurfaceHolder holder) {
//Put stuff that needs destructed here.
boolean retry = true;
_thread.setRunning(false);
while (retry) {
try {
_thread.join();
retry = false;
} catch (InterruptedException e) {
// will will try again and again
//TODO: figure it out....
}
}
}
public boolean onTouchEvent(MotionEvent event) {
int numPointers = event.getPointerCount();
int ptrIdx = 0;
int touch = event.getActionMasked();
if (touch == MotionEvent.ACTION_DOWN) {
while (ptrIdx < numPointers) {
int id = event.getPointerId(ptrIdx);
float xp = event.getX(ptrIdx) / width;
if (xp > 0.6) {
_thread.shieldFront = false;
}
if (xp > 0.6 && !attacks) {
attacks = true;
_thread.attackandDefendToggle(true);
} else if (xp > 0.6 && attacks) {
attacks = false;
_thread.attackandDefendToggle(false);
} else if ((xp < 0.4 && xp > 0.2) && !movedRight) {
movedRight = true;
_thread.moveRight(true);
} else if ((xp < 0.4 && xp > 0.2) && movedRight) {
movedRight = false;
_thread.moveRight(false);
} else if (xp < 0.2 && !movedLeft) {
movedLeft = true;
_thread.moveLeft(true);
} else if (xp < 0.2 && movedLeft) {
movedLeft = false;
_thread.moveLeft(false);
}
ptrIdx++;
}
}
if (touch == MotionEvent.ACTION_UP) {
_thread.moveLeft(false);
_thread.moveRight(false);
_thread.attackandDefendToggle(false);
attacks = false;
_thread.shieldFront = true;
}
return true;
}
class GameThread extends Thread {
/****************************
* Public functions *
****************************/
public GameThread(SurfaceHolder surfaceHolder, GameView panel) {
_surfaceHolder = surfaceHolder;
_panel = panel;
// put sounds here.
soundPool = new SoundPool(20, AudioManager.STREAM_MUSIC, 0);
//more sounds later
//TODO: create sounds
}
/************************************************
* update() function updates all variables, *
* such as physics, Canvas draw points, score *
* life, etc.. It is called before draw. *
************************************************/
private void update() {
// all the values I want updated with each callback
}
/************************************************
* draw() function creates images on screen, *
* but it performs no logic. *
************************************************/
private void draw(Canvas canvas) {
if (canvas == null) {
return;
}
//Draw stuff on screen
}
public void initialize() {
// Values I want the program to start with;
}
public void setRunning(boolean run) {
_run = run;
}
//Code below actually runs the thread.
#Override
public void run() {
Canvas c;
while (_run) {
c = null;
try {
c = _surfaceHolder.lockCanvas(null);
synchronized (_surfaceHolder) {
// Update the game state
update();
// Draw image
draw(c);
}
} finally {
// do this in a finally so that if an exception is thrown
// during the above, we don't leave the Surface in an
// inconsistent state
if (c != null) {
_surfaceHolder.unlockCanvasAndPost(c);
}
}
}
}
}
I'm working on a Android game using SurfaceView, the game works fine, so I want to pause it when the user hits HOME key but when getting back the SurfaceView disappears(black background Edit: sometimes the drawing appears) and buttons are inactive in both cases then I get ANR.
public class MainActivity extends Activity {
private Game game;
MainPanel mp;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
game = new Game();
LoadLevel();
Init();
setContentView(R.layout.main);//contains surfaceview and buttons
mp = (MainPanel) findViewById(R.id.SurfaceView01);
mp.Init(game,this);
Button btnTop = (Button) findViewById(R.id.buttonTop);
Button btnBottom = (Button) findViewById(R.id.buttonBottom);
Button btnLeft = (Button) findViewById(R.id.buttonLeft);
Button btnRight = (Button) findViewById(R.id.buttonRight);
btnTop.setOnClickListener...
}
private void Init() {
...
}
public void LoadLevel() {
...
}
#Override
protected void onResume() {
super.onResume();
Log.d("Tag","Resume");
}
#Override
protected void onPause() {
super.onPause();
Log.d("Tag","Pause");
}
}
public class MainPanel extends SurfaceView implements SurfaceHolder.Callback {
private MainThread thread;
private Game game;
private MainActivity act;
public MainPanel(Context context, AttributeSet attrs) {
super(context,attrs);
getHolder().addCallback(this);
}
public MainThread getThread() {
return thread;
}
public void Init(Game game, MainActivity act){
this.game = game;
this.act = act;
}
public void Move(int dir) {
...
}
public void update() {
...
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width,int height) {}
#Override
public void surfaceCreated(SurfaceHolder holder) {
if (thread == null) {
thread = new MainThread(getHolder(), this);
thread.Start();
thread.start();
}
}
#Override
public void surfaceDestroyed(SurfaceHolder holder) {
thread.Stop();
boolean retry = true;
while (retry) {
try {
thread.join();
retry = false;
} catch (InterruptedException e) {}
}
thread = null;
}
#Override
protected void onDraw(Canvas canvas) {
canvas.drawColor(Color.BLACK);
//drawing...
}
}
public class MainThread extends Thread {
private final static int MAX_FPS = 30;
private final static int MAX_FRAME_SKIPS = 3;
private final static int FRAME_PERIOD = 1000 / MAX_FPS;
private SurfaceHolder surfaceHolder;
private MainPanel gamePanel;
private boolean run = false;
private boolean start = false;
public MainThread(SurfaceHolder surfaceHolder, MainPanel gamePanel) {
super();
this.surfaceHolder = surfaceHolder;
this.gamePanel = gamePanel;
}
public void Start() {
this.run = true;
this.start = true;
}
public void Pause() {
this.start = false;
}
public void Resume() {
this.start = true;
}
public void Stop() {
this.run = false;
}
#Override
public void run() {
Canvas c = null;
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
while(run) {
while(start) {
try {
c = surfaceHolder.lockCanvas(null);
synchronized (surfaceHolder) {
beginTime = System.currentTimeMillis();
framesSkipped = 0; // resetting the frames skipped
// update game state
gamePanel.update();
// render state to the screen draws the canvas on the panel
if(c!=null) gamePanel.onDraw(c);
// 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 update without rendering
gamePanel.update();
// add frame period to check if in next frame
sleepTime += FRAME_PERIOD;
framesSkipped++;
}
}
} finally {
if (c != null) surfaceHolder.unlockCanvasAndPost(c);
}
}
}
}
}
Thx to this post why is my runnable giving ANR? I could solve the problem, I isolated the MainThread from the UI thread for further details check the best answer.
I have created a class like this:
public class nir extends Activity implements OnClickListener{
/** Called when the activity is first created. */
ImageButton btnNew, btnPlay, btnSignUp;
TextView txtTitle;
Typeface font;
//sprite
SurfaceView pet;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
font = Typeface.createFromAsset(getAssets(), "fonts/aaaiight.ttf");
btnNew = (ImageButton)findViewById(R.id.btnNew);
btnNew.setOnClickListener(this);
btnPlay = (ImageButton)findViewById(R.id.btnPlay);
btnPlay.setOnClickListener(this);
txtTitle = (TextView)findViewById(R.id.lblTama);
txtTitle.setTypeface(font);
//sprite
//setContentView(new MainGamePanel(this));
}
}
Now I want to make that SurfaceView show any sprite. I've tried
<view class="Tamagotchi.nir.MainGamePanel...">
in my XML, but it gives me a "Force Close" dialog.
I also have this class:
public class MainGamePanel extends SurfaceView implements
SurfaceHolder.Callback {
private static final String TAG = MainGamePanel.class.getSimpleName();
private MainThread thread;
private Sprite elaine;
// the fps to be displayed
private String avgFps;
public void setAvgFps(String avgFps) {
this.avgFps = avgFps;
}
public MainGamePanel(Context context) {
super(context);
// adding the callback (this) to the surface holder to intercept events
getHolder().addCallback(this);
// create Elaine and load bitmap
elaine = new Sprite(
BitmapFactory.decodeResource(getResources(),
R.drawable.walk_elaine),
10, 50, // initial position
30, 47, // width and height of sprite
5, 5); // FPS and number of frames in the animation
// create the game loop thread
thread = new MainThread(getHolder(), this);
// make the GamePanel focusable so it can handle events
setFocusable(true);
}
#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) {
// handle touch
}
return true;
}
public void render(Canvas canvas) {
canvas.drawColor(Color.BLACK);
elaine.draw(canvas);
// display fps
displayFps(canvas, avgFps);
}
/**
* 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() {
elaine.update(System.currentTimeMillis());
}
private void displayFps(Canvas canvas, String fps) {
if (canvas != null && fps != null) {
Paint paint = new Paint();
paint.setARGB(255, 255, 255, 255);
canvas.drawText(fps, this.getWidth() - 50, 20, paint);
}
}
}
What is the reason for the crash?
Take out view class =
in front of Your package name all you need is the package name.YourSurfaceViewClass
This is how your code should look
<Tamagotchi.nir.MainGamePanel.YourSurfaceViewClassName
android:id="#+id/myView"
android:layout_width="fill_parent"
android:layout_height="fill_parent"/>