javaandroidsurfaceholder

lockCanvas failing when screen orientation changes


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

Here's 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);
            }
        }
    }
}

And this is the exception that happens when the orientation 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 the only thing that I can do so far is to wrap unlockCanvasAndPost and lockCanvas in a try catch to ignore IllegalArgumentException.


Solution

  • In draw(), I'd try moving handler.removeCallbacks(drawRunner); just before the try block. It could be that onOffsetsChanged() is getting called on orientation change, and that the previous thread on the handler might not have called unlockCanvasAndPost(c) yet, which explains why you're getting an error with lockCanvas() at that point. However, this shouldn't be the case if the code you've posted here exactly matches what you're running locally, since you haven't overriden onOffsetsChanged().

    Another thing you could try is overriding onSurfaceChanged() and clearing 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 of the examples regarding WallpaperService that I've read online have a try-finally block with the lock/unlock canvas logic, so I wouldn't be worried.