Java – lockCanvas fails when screen orientation changes

lockCanvas fails when screen orientation changes… here is a solution to the problem.

lockCanvas fails when screen orientation changes

I’m trying to build a live wallpaper in Android, but the app crashes when the orientation changes. It looks like it’s crashing when trying to lock the Canvas on the surface bracket, but I’m not sure what I can do to prevent it.

This is the class:

public class LiveWallpaperService extends WallpaperService
{
    public void onCreate() {
        super.onCreate();
    }

public void onDestroy() {
        super.onDestroy();
    }

public Engine onCreateEngine() {
        return new MyWallpaperEngine();
    }

class MyWallpaperEngine extends Engine
    {
        private final Handler handler = new Handler();
        private final Runnable drawRunner = new Runnable() {
            @Override
            public void run() {
                draw();
            }
        };
        private boolean visible = true;

Paint paint;

MyWallpaperEngine() {
            paint = new Paint();
        }

public void onCreate(SurfaceHolder surfaceHolder) {
            super.onCreate(surfaceHolder);
        }

@Override
        public void onVisibilityChanged(boolean visible) {
            this.visible = visible;

if (visible) {
                handler.post(drawRunner);
            }
            else {
                handler.removeCallbacks(drawRunner);
            }
        }

@Override
        public void onSurfaceDestroyed(SurfaceHolder holder) {
            super.onSurfaceDestroyed(holder);
            this.visible = false;
            handler.removeCallbacks(drawRunner);
        }

public void onOffsetsChanged(float xOffset, float yOffset, float xStep, float yStep, int xPixels, int yPixels) {
            draw();
        }

void draw() {
            final SurfaceHolder holder = getSurfaceHolder();

Canvas c = null;

try {
                c = holder.lockCanvas();

if (c != null) {
                     Paint stuff here.
                }
            }
            finally {
                if (c != null) {
                    holder.unlockCanvasAndPost(c);
                }
            }

handler.removeCallbacks(drawRunner);
            if (visible) {
                handler.postDelayed(drawRunner, 10);
            }
        }
    }
}

This is an anomaly that occurs when the direction changes:

E/StudioProfiler: JVMTI error: 15(JVMTI_ERROR_THREAD_NOT_ALIVE) 
E/Surface: dequeueBuffer failed (No such device)
E/BaseSurfaceHolder: Exception locking surface
   java.lang.IllegalArgumentException
           at android.view.Surface.nativeLockCanvas(Native Method)
           at android.view.Surface.lockCanvas(Surface.java:318)
           at com.android.internal.view.BaseSurfaceHolder.internalLockCanvas(BaseSurfaceHolder.java:194)
           at com.android.internal.view.BaseSurfaceHolder.lockCanvas(BaseSurfaceHolder.java:158)
           at android.service.wallpaper.WallpaperService$Engine$1.lockCanvas(WallpaperService.java:262)
           at greencell.bitpatternswallpaper.LiveWallpaperService$MyWallpaperEngine.draw(LiveWallpaperService.java:206)
           at greencell.bitpatternswallpaper.LiveWallpaperService$MyWallpaperEngine$1.run(LiveWallpaperService.java:51)
           at android.os.Handler.handleCallback(Handler.java:790)
           at android.os.Handler.dispatchMessage(Handler.java:99)
           at android.os.Looper.loop(Looper.java:164)
           at android.app.ActivityThread.main(ActivityThread.java:6494)
           at java.lang.reflect.Method.invoke(Native Method)
           at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:438)
           at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:807)

Update:

I’ve checked many other threads that seem to have the same issue, but so far the only thing I’ve been able to do is try wrapping unlockCanvasAndPost and lockCanvas catch to ignore the IllegalArgumentException.

Solution

In draw(

), I tried to add handler.removeCallbacks(drawRunner); Move before try block. It may be that onOffsetsChanged() was called when the direction changed, and the previous thread on the handler may not have called unlockCanvasAndPost(c) yet, which explains why you got the lockCanvas() error at that time. However, this should not be the case if the code you post here exactly matches the code you run locally, because you did not override onOffsetsChanged().

Another thing you can try is to override onSurfaceChanged() and clear the handler queue like this:

@Override
public void onSurfaceChanged(SurfaceHolder holder, int format, int width, int height) {
    handler.removeCallbacks(drawRunner);
    super.onSurfaceChanged(holder, format, width, height);
}

Ultimately, all the examples I’ve read online about WallpaperService have a try-finally block logic with locking/unlocking Canvas, so I wouldn’t be worried.

Related Problems and Solutions