Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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?

like image 818
Trevor Avatar asked Apr 28 '12 14:04

Trevor


1 Answers

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

like image 101
Andy Avatar answered Oct 10 '22 01:10

Andy