null Canvas in SurfaceView Thread, despite stopping Thread in surfaceDestroyed() - only on Android 4 / ICS

android, surfaceview

Solution

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

Problem

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?

Original source