I have a SurfaceView extension where the bare bones of it are implemented as in the Lunar Lander example. That is, the run()
method of the drawing Thread
is essentially:
public void run() {
while (mRun) {
Canvas c;
try {
c = mSurfaceHolder.lockCanvas();
synchronized (mSurfaceHolder) {
doDraw(c); // Main drawing method - not included in this code snippet
}
}
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) {
mSurfaceHolder.unlockCanvasAndPost(c);
}
}
}
}
}
And the Thread
is properly stopped when the surface is destroyed:
public void surfaceDestroyed(SurfaceHolder holder) {
// we have to tell thread to shut down & wait for it to finish, or else
// it might touch the Surface after we return and explode
boolean retry = true;
thread.setRunning(false);
while (retry) {
try {
thread.join();
retry = false;
}
catch (InterruptedException e) {
}
}
}
On devices I have usually tested on to date (HTC Desire, Desire HD, and Archos 101 which between them have OS 2.2 and 2.3.3 if I remember right) there has never been a problem with the above. That is, when the surface is destroyed because the user backs out of the Activity
or another Activity
is invoked on top, the code within surfaceDestroyed()
always ensures that mSurfaceHolder.lockCanvas()
would never be called for it to return null
.
The difference I've found on my new HTC One X which is running Android 4 / ICS, however, is that during the call to method surfaceDestroyed()
(i.e. code within that method is still executing) my drawing Thread
would be given a null
canvas from mSurfaceHolder.lockCanvas()
. This would of course cause an application crash. On my One X, this will happen every single time the surface is destroyed - whether it be due to rotating the phone, backing out of the Activity
, etc.
I'm confused about this because I was under the impression that mSurfaceHolder.lockCanvas()
should return a non-null
Canvas
until surfaceDestroyed()
has actually exited. Indeed, this is what the Javadoc says:
This is called immediately before a surface is being destroyed. After returning from this call, you should no longer try to access this surface. If you have a rendering thread that directly accesses the surface, you must ensure that thread is no longer touching the Surface before returning from this function.
My solution for now is to just check for null
. This works fine:
if(c != null){
doDraw(c); // Main drawing method - not included in this code snippet
}
But, any ideas why I'm suddenly having to do this for Android 4 / ICS?
Just to elaborate on my comment, it seems that there is a bug ticket open for this change in behaviour, which you can find here: http://code.google.com/p/android/issues/detail?id=38658. If it's effected you, it might be worth staring it, just so it raises it's importance a bit!
Personally, I've seen this myself, just by using the lunar lander example which comes with the latest android SDK. I put it on my HTC Sensation XE (4.0.3) and on orientation change, I get some null canvases being returned before the surface is destroyed.
So the workaround I use is just to double check that the canvas is not null before passing it to my update and render methods.
Andy
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With