I have implemented a gameloop to work on SurfaceView, it works perfectly but when scaling down or moving canvas it renders snapshots of scaling behind the canvas which is annoying.
So I want to clear my view background after onDraw() calls. I tried using view.setBackgroundColor() from the gameloop or inside onDraw() but android claims that I can use it from another thread.
Is there any equivalent method to do so?
public class GameLoopThread extends Thread
{
private MapView view;
private boolean running = false;
static final long FPS = 30;
public GameLoopThread(MapView view)
{ this.view = view; }
public void setRunning(boolean run)
{ running = run; }
#Override
public void run()
{
long ticksPS = 1000 / FPS;
long startTime;
long sleepTime;
while (running)
{
Canvas c = null;
startTime = System.currentTimeMillis();
try
{
c = view.getHolder().lockCanvas();
synchronized (view.getHolder())
{
view.onDraw(c);
view.setBackgroundColor(Color.WHITE); // fails on this
}
}
finally
{
if (c != null)
view.getHolder().unlockCanvasAndPost(c);
}
sleepTime = ticksPS - (System.currentTimeMillis() - startTime);
try
{
if (sleepTime > 0) sleep(sleepTime);
else sleep(10);
}
catch (Exception e){}
}
}
}
Update
I don't want to use view.setBackgroundColor() instead I want to use something equivalent to draw behin the canvas
You can't update UI from a separate thread instead you can use runOnUiThread
You can only "Change" views from the UIThread. So a possible solution would be passing your Activity-Context and use
activityContext.runOnUiThread
Hope it helps
here it is better to use the Handler, because GameLoopThread has no Context
android.os.Handler handler = new android.os.Handler();
handler.post(new Runnable() {
#Override
public void run() {
//update here
}
});
make private MapView view; to final
You can update UI thread from handlers, asynctasks, or runOnUIThread method. You cannot do that from a normal thread
Thanks all for posting answers.
I have found my need which in its turn works as setBackgroundColor()
It's as simple as calling drawColor()
#Override
protected void onDraw(Canvas canvas)
{
canvas.drawColor(Color.WHITE); // fill the whole canvas with white
// rest of drawing
}
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 am coding a snake game in Android, and I am blocked by the fact that i can't draw a wall using a loop inside onDraw method, the wall just don't show up.
Is there a way i can draw the wall so that the onDraw will keep it intact and keep on redrawing others things (snake and objects) ?
GameView:
public void surfaceCreated(SurfaceHolder holder) {
gameLoopThread.setRunning(true);
gameLoopThread.start();
}
#Override
protected void onDraw(final Canvas canvas) {
canvas.drawColor(Color.WHITE);
if(drawWall) {
int i = 0;
while(i < getWidth()) {
canvas.drawBitmap(wall.getImage(), i, 0, null);
i += 17;
}
drawWall = false;
}
x += dx;
y += dy;
}
GameLoop:
public class GameLoopThread extends Thread {
private GameView view;
private boolean running = false;
public GameLoopThread(GameView view) {
this.view = view;
}
public void setRunning(boolean run) {
running = run;
}
#Override
public void run() {
while (running) {
Canvas c = null;
try {
c = view.getHolder().lockCanvas();
synchronized (view.getHolder()) {
view.onDraw(c);
}
} finally {
if (c != null) {
view.getHolder().unlockCanvasAndPost(c);
}
}
}
}
}
I don't know what your view hierarchy is, however, why not make your life easier and just have an ImageView behind your SurfaceView with your walls on it? Or have a FrameLayout contain your SurfaceView and have the walls as a background image for the FrameLayout?The collision with the walls is going to be handled in the code and the graphical representation of the walls is really just for the users benefit.
Put the images of the walls in the background and let Android worry about it, in your SurfaceView just concentrate of redrawing the snake and the dots.
That being said, looks like you're drawing in a background thread. You can't actually do that on Android and you should be drawing in the main thread.
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.
For normal Activity with the help of ImageView we are able to attain Frame Animation ("Through some online tutorial i done that")
img = (ImageView) findViewById(R.id.myimage);
img.setBackgroundResource(R.drawable.loadimage);
AnimationDrawable frameAnimation = (AnimationDrawable) img.getBackground();
frameAnimation.start();
but here in Canvas i'm using Bitmap to get an image to the view, their i'm stuck..
Resources res = getResources();
Bitmap bitmap = BitmapFactory.decodeResource(res, R.drawable.loadimage);
is their any way to do frame animation in canvas..?
You can create an animated custom view using SurfaceView with a Thread to schedule the animation times properly.
The idea is to extend from SurfaceView, get the Canvas from the SurfaceHolder, draw on it, post the draw on the screen, and repeat (done in a background thread). A basic example:
public class CustomView extends SurfaceView implements Runnable {
Thread animationThread = null;
SurfaceHolder holder;
volatile boolean running = false;
public CustomView(Context context) {
super(context);
holder = this.getHolder();
}
// called from Activity's onResume() to start the thread
public void resume() {
running = true;
animationThread = new Thread(this);
animationThread.start();
}
// called from Activity's onPause() to end the thread
public void pause() {
running = false;
while(true) {
try {
animationThread.join();
break;
} catch (InterruptedException e) { }
}
}
// run until pause() is called
#Override
public void run() {
while(running) {
if(!holder.getSurface().isValid())
continue;
Canvas canvas = holder.lockCanvas();
// your drawing logic goes here:
// - calculate the time of the frame
// - draw a different bitmap each time ...
canvas.drawBitmap(...)
holder.unlockCanvasAndPost(canvas);
}
}
}
To handle the size of the view, you would need to override onMeasure() and onSizeChanged().
For a better understanding of how this works:
http://blog.infrared5.com/2011/06/android-graphics-and-animation-part-i/
http://blog.infrared5.com/2011/07/android-graphics-and-animation-part-ii-animation/
http://developer.android.com/reference/android/view/SurfaceView.html