开发者

Android's WallpaperService.Engine issues, workarounds required

During development of Live Wallpapers I jus got two issues and want to find the best possible workarounds.

Issue#1: WallpaperService.Engine.onSurfaceCreated() and WallpaperService.Engine.onSurfaceChanged() called after WallpaperService.Engine.onDestroyed()

Under some circumstances Android call the WallpaperService.Engine.onSurfaceCreated() and WallpaperService.Engine.onSurfaceChanged() after WallpaperService.Engine.onDestroyed() was called. It is against the WallpaperService.Engine execution protocol defined by documentation.

My current solution is just to have the explicit flag (mAlreadyDestroyed) which false by default but set to true in onDestroy() callback. The WallpaperService.Engine.onSurfaceCreated() and WallpaperService.Engine.onSurfaceChanged() check this flag and do nothing if it is true. Have anyone face this issue too and how you resolve it ?

Below is a simple code you may use to check for this issue:

public class LWService extends WallpaperService {

    /**
     * Will show the bug with calling {@link #onSurfaceCreated(SurfaceHolder)}
     * and other surface callbacks after {@link #onDestroy()}.
     * 
     */
    private class LWEngineTest1 extends Engine {

        /**
         * Will be set to <code>true</code> in {@link #onDestroy()}.
         */
        private boolean mAlreadyDestroyed = false;

        /**
         * Log debug level message with adding object instance info to better
         * LogCat readability.
         * 
         * @param message
         *            message to log
         */
        private void logD(final String message) {
            Log.d("LW_BUG_TEST", this.toString() + ":" + message);
        }

        /**
         * Log error level message with adding object instance info to better
         * LogCat readability.
         * 
         * @param message
         *            message to log
         */
        private void logE(final String message) {
            Log.e("LW_BUG_TEST", this.toString() + ":" + message);
        }

        @Override
        public void onCreate(SurfaceHolder surfaceHolder) {
            logD("onCreate()");
        }

        @Override
        public void onDestroy() {
            logD("onDestroy()");
            mAlreadyDestroyed = true;
        }

        @Override
        public void onSurfaceCreated(SurfaceHolder holder) {
            logD("onSurfaceCreated()");
            if (mAlreadyDestroyed) {
                logE("onSurfaceCreated() after onDestroy()");
            }
        }

        @Override
        public void onSurfaceChanged(SurfaceHolder holder, int format, int width, int height) {
            logD("onSurfaceChanged()");
            if (mAlreadyDestroyed) {
                logE("onSurfaceChanged() after onDestroy()");
            }
        }

        @Override
        public void onSurfaceDestroyed(SurfaceHolder holder) {
            logD("onSurfaceDestroyed()");
            if (mAlreadyDestroyed) {
                logE("onSurfaceDestroyed() after onDestroy()");
            }

            try {
                // To reveal the bug, without this line you may not got the
                // issue. Of course it is absolutely synthetic but allow to get
                // the issue guaranteed
                Thread.sleep(4000);
            } catch (InterruptedException exc) {
            }
        }
    }

    @Override
    public Engine onCreateEngine() {
        return new LWEngineTest1();
    }
}

And following is what I get in log on Samsung Galaxy S (Android 2.2.1). Just set this sample Wallpaper as current and then select another one (only related log entries preserved):

08-14 14:53:55.964: DEBUG/LW_BUG_TEST(19274): lwtests.LWService$LWEngineTest1@48008a28:onCreate()
08-14 14:53:55.980: DEBUG/LW_BUG_TEST(19274): lwtests.LWService$LWEngineTest1@48008a28:onSurfaceCreated()
08-14 14:53:55.980: DEBUG/LW_BUG_TEST(19274): lwtests.LWService$LWEngineTest1@48008a28:onSurfaceChanged()
08-14 14:54:17.651: DEBUG/LW_BUG_TEST(19274): lwtests.LWService$LWEngineTest1@4800f0b0:onCreate()
08-14 14:54:17.667: DEBUG/LW_BUG_TEST(19274): lwtests.LWService$LWEngineTest1@4800f0b0:onSurfaceCreated()
08-14 14:54:17.667: DEBUG/LW_BUG_TEST(19274): lwtests.LWService$LWEngineTest1@4800f0b0:onSurfaceChanged()
08-14 14:54:18.261: DEBUG/LW_BUG_TEST(19274): lwtests.LWService$LWEngineTest1@48008a28:onSurfaceDestroyed()
08-14 14:54:22.265: DEBUG/LW_BUG_TEST(19274): lwtests.LWService$LWEngineTest1@48008a28:onDestroy()
08-14 14:54:26.675: DEBUG/LW_BUG_TEST(19274): lwtests.LWService$LWEngineTest1@4800f0b0:onSurfaceDestroyed()
08-14 14:54:30.675: DEBUG/LW_BUG_TEST(19274): lwtests.LWService$LWEngineTest1@4800f0b0:onDestroy()
08-14 14:54:30.687: DEBUG/LW_BUG_TEST(19274): lwtests.LWService$LWEngineTest1@4800f0b0:onSurfaceCreated()
08-14 14:54:30.687: ERROR/LW_BUG_TEST(19274): lwtests.LWService$LWEngineTest1@4800f0b0:onSurfaceCreated() after onDestroy()
08-14 14:54:30.687: DEBUG/LW_BUG_TEST(19274): lwtests.LWService$LWEngineTest1@4800f0b0:onSurfaceChanged()
08-14 14:54:30.687: ERROR/LW_BUG_TEST(19274): lwtests.LWService$LWEngineTest1@4800f0b0:onSurfaceChanged() after onDestroy()

Issue#2: WallpaperService.Engine.onSurfaceDestroyed() called after surface was actually destroyed

Under some circumstances Android may call the WallpaperService.Engine.onSurfaceDestroyed() after surface was actually destroyed. It is against the SurfaceHolder.Callback.onSurfaceDestroyed() specification. This issue is rather very specific and may be unnoticed even if occur in your code. The most time you will notice that issue if you has separate rendering thread. The rendering thread may notice died surface even before your main application's thread receive the mentioned WallpaperService.Engine.onSurfaceDestroyed(). In my case of OpenGL rendering I just got the EGL_BAD_NATIVE_WINDOW when eglSwapBuffers() are executed.

My current solution is just to ignore the specified EGL error but I really don't like such kind of solutions. My code full of self-checking assertions and error conditions checks and I don't like to remove these assertions and checks (and specific error code ignoring is such kind of thing). Is there are any solutions exists for this issue ?

Below is a simple code you may use to check for this issue:

public class LWService extends WallpaperService {

    /**
     * Will show the bug with non-conform to documentation (specification)
     * {@link #onSurfaceDestroyed(SurfaceHolder)}.
     */
    private class LWEngineTest2 extends Engine {

        /**
         * Log error level message with adding object instance info to better
         * LogCat readability.
         * 
         * @param message
         *            message to log
         */
        private void logE(final String message) {
            Log.e("LW_BUG_TEST", this.toString() + ":" + message);
        }

        @Override
        public void onSurfaceDestroyed(SurfaceHolder holder) {
            if (holder.getSurface().isValid() && null == holder.lockCanvas()) {
                logE("onSurfaceDestroyed() : uuups ... broken surface");
            }

            // If you have separate rendering thread it may already notice that
            // surface already invalid and encounter problems due to that fact.
            // E.g. eglSwapBuffers() may generate EGL_INVALID_NATIVE_WINDOW
            // error.

            // mRenderingThread.releaseSurface();
        }
    }

    @Override
    public Engine onCreateEngine() {
        return new LWEngineTest1();
    }
}

And following is what I get in log on Acer A500 (Android 3.1). Just select this Wallpaper for preview and then press Back button in preview activity (only related log entries preserved):

08-14 16:10:55.580: ERROR/Surface(31787): Surface (identity=1298) state = -19
08-14 16:10:55.660: ERROR/Surface(31787): getBufferLocked(0, 0, 0, 0, 00000033) failed (No such device)
08-14 16:10:55.660: ERROR/Surface(31787): dequeueBuffer failed (No such device)
08-14 16:10:55.660: ERROR/BaseSurfaceHolder(31787): Exception locking surface
08-14 16:10:55.660: ERROR/BaseSurfaceHolder(31787): java.lang.IllegalArgumentException
08-14 16:10:55.660: ERROR/BaseSurfaceHolder(31787):     at android.view.Surface.lockCanvasNative(Native Method)
08-14 16:10:55.660: ERROR/BaseSurfaceHolder(31787):     at android开发者_如何学C.view.Surface.lockCanvas(Surface.java:346)
08-14 16:10:55.660: ERROR/BaseSurfaceHolder(31787):     at com.android.internal.view.BaseSurfaceHolder.internalLockCanvas(BaseSurfaceHolder.java:184)
08-14 16:10:55.660: ERROR/BaseSurfaceHolder(31787):     at com.android.internal.view.BaseSurfaceHolder.lockCanvas(BaseSurfaceHolder.java:157)
08-14 16:10:55.660: ERROR/BaseSurfaceHolder(31787):     at lwtests.LWService$LWEngineTest2.onSurfaceDestroyed(LWService.java:271)
08-14 16:10:55.660: ERROR/BaseSurfaceHolder(31787):     at android.service.wallpaper.WallpaperService$Engine.reportSurfaceDestroyed(WallpaperService.java:773)
08-14 16:10:55.660: ERROR/BaseSurfaceHolder(31787):     at android.service.wallpaper.WallpaperService$Engine.detach(WallpaperService.java:790)
08-14 16:10:55.660: ERROR/BaseSurfaceHolder(31787):     at android.service.wallpaper.WallpaperService$IWallpaperEngineWrapper.executeMessage(WallpaperService.java:902)
08-14 16:10:55.660: ERROR/BaseSurfaceHolder(31787):     at com.android.internal.os.HandlerCaller$MyHandler.handleMessage(HandlerCaller.java:61)
08-14 16:10:55.660: ERROR/BaseSurfaceHolder(31787):     at android.os.Handler.dispatchMessage(Handler.java:99)
08-14 16:10:55.660: ERROR/BaseSurfaceHolder(31787):     at android.os.Looper.loop(Looper.java:132)
08-14 16:10:55.660: ERROR/BaseSurfaceHolder(31787):     at android.app.ActivityThread.main(ActivityThread.java:4025)
08-14 16:10:55.660: ERROR/BaseSurfaceHolder(31787):     at java.lang.reflect.Method.invokeNative(Native Method)
08-14 16:10:55.660: ERROR/BaseSurfaceHolder(31787):     at java.lang.reflect.Method.invoke(Method.java:491)
08-14 16:10:55.660: ERROR/BaseSurfaceHolder(31787):     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:841)
08-14 16:10:55.660: ERROR/BaseSurfaceHolder(31787):     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:599)
08-14 16:10:55.660: ERROR/BaseSurfaceHolder(31787):     at dalvik.system.NativeStart.main(Native Method)
08-14 16:10:55.660: ERROR/LW_BUG_TEST(31787): lwtests.LWService$LWEngineTest2@407ba3b8:onSurfaceDestroyed() : uuups ... broken surface

I have reported both these issues in Android issues tracker (19243, 19245) but practically it is more interesting to know how to resolve these issues for already released versions of Androids. This is why I am asking here for solutions anyone may know. And while I have already the own workarounds (may be will be usefull to someones) I also want to know another possibilities to fight these issues.

Thanks in advance.


There are more than one engine can be executed in parallel. You have to manage them. To check this, just add an id to every engine you create. You can verify this in this way:

public class LWService extends WallpaperService {

    static int engineCounter;

    /** 
     * Will show the bug with calling {@link #onSurfaceCreated(SurfaceHolder)} 
     * and other surface callbacks after {@link #onDestroy()}. 
     *  
     */ 
    private class LWEngineTest1 extends Engine {

        public int id;

        public LWEngineTest1()
        {
          id=++engineCounter;
        }

        /** 
         * Will be set to <code>true</code> in {@link #onDestroy()}. 
         */ 
        private boolean mAlreadyDestroyed = false;

        /** 
         * Log debug level message with adding object instance info to better 
         * LogCat readability. 
         *  
         * @param message 
         *            message to log 
         */ 
        private void logD(final String message) {
            Log.d("LW_BUG_TEST", this.toString() + ":" + message);
        } 

        /** 
         * Log error level message with adding object instance info to better 
         * LogCat readability. 
         *  
         * @param message 
         *            message to log 
         */ 
        private void logE(final String message) {
            Log.e("LW_BUG_TEST", this.toString() + ":" + message);
        } 

        @Override 
        public void onCreate(SurfaceHolder surfaceHolder) {
            logD("onCreate() engineId="+id); 
        } 

        @Override 
        public void onDestroy() { 
            logD("onDestroy() engineId="+id); 
            mAlreadyDestroyed = true;
        } 

        @Override 
        public void onSurfaceCreated(SurfaceHolder holder) {
            logD("onSurfaceCreated() engineId="+id); 
            if (mAlreadyDestroyed) {
                logE("onSurfaceCreated() after onDestroy()"); 
            } 
        } 

        @Override 
        public void onSurfaceChanged(SurfaceHolder holder, int format, int width, int height) {
            logD("onSurfaceChanged() engineId="+id); 
            if (mAlreadyDestroyed) {
                logE("onSurfaceChanged() after onDestroy()"); 
            } 
        } 

        @Override 
        public void onSurfaceDestroyed(SurfaceHolder holder) {
            logD("onSurfaceDestroyed() engineId="+id); 
            if (mAlreadyDestroyed) {
                logE("onSurfaceDestroyed() after onDestroy()"); 
            } 

            try { 
                // To reveal the bug, without this line you may not got the 
                // issue. Of course it is absolutely synthetic but allow to get 
                // the issue guaranteed 
                Thread.sleep(4000);
            } catch (InterruptedException exc) {
            } 
        } 
    } 

    @Override 
    public Engine onCreateEngine() {
        return new LWEngineTest1(); 
    } 
} 

I notice that on different devices there are slightly different behaviours. So i suggest you to test your solution on different devices.


I tried out your code on an Incredible running Cyanogen 7.0.3 (Gingerbread 2.3.3). I don't see the problem you're seeing.

Here are my log results after going through Picker > Preview > Set Wallpaper:

08-24 07:47:04.261: DEBUG/LW_BUG_TEST(32534): LWService$LWEngineTest1@40630938:onCreate()
08-24 07:47:04.301: DEBUG/LW_BUG_TEST(32534): LWService$LWEngineTest1@40630938:onSurfaceCreated()
08-24 07:47:04.301: DEBUG/LW_BUG_TEST(32534): LWService$LWEngineTest1@40630938:onSurfaceChanged()
08-24 07:47:10.187: DEBUG/LW_BUG_TEST(32534): LWService$LWEngineTest1@4062d100:onCreate()
08-24 07:47:10.227: DEBUG/LW_BUG_TEST(32534): LWService$LWEngineTest1@4062d100:onSurfaceCreated()
08-24 07:47:10.227: DEBUG/LW_BUG_TEST(32534): LWService$LWEngineTest1@4062d100:onSurfaceChanged()
08-24 07:47:10.858: DEBUG/LW_BUG_TEST(32534): LWService$LWEngineTest1@40630938:onSurfaceDestroyed()
08-24 07:47:14.862: DEBUG/LW_BUG_TEST(32534): LWService$LWEngineTest1@40630938:onDestroy()

Then after setting a completely unrelated wallpaper, that instance is destroyed:

08-24 07:48:33.268: DEBUG/LW_BUG_TEST(32534): LWService$LWEngineTest1@4062d100:onSurfaceDestroyed()
08-24 07:48:37.272: DEBUG/LW_BUG_TEST(32534): LWService$LWEngineTest1@4062d100:onDestroy()

Are here are my results after Picker > Preview > Back (no Set Wallpaper):

08-24 07:49:50.133: DEBUG/LW_BUG_TEST(32534): LWService$LWEngineTest1@4063eb20:onCreate()
08-24 07:49:50.173: DEBUG/LW_BUG_TEST(32534): LWService$LWEngineTest1@4063eb20:onSurfaceCreated()
08-24 07:49:50.173: DEBUG/LW_BUG_TEST(32534): LWService$LWEngineTest1@4063eb20:onSurfaceChanged()
08-24 07:49:54.157: DEBUG/LW_BUG_TEST(32534): LWService$LWEngineTest1@4063eb20:onSurfaceDestroyed()
08-24 07:49:58.161: DEBUG/LW_BUG_TEST(32534): LWService$LWEngineTest1@4063eb20:onDestroy()
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜