5

I've created my first live wallpaper implementing drawing in a separate thread. So now I have a WallpaperService and my WallpaperPainter who does the job. The problem is that I getting a IllegalArgumentException in unlockCanvasAndPost method on some of devices (Samsung Note is the one). I've read all recomendations I could find but couldn't fix that bug. Seems like the unlockCanvasAndPost is called when surface is destroyed so canvas is invalid. Here is the essential parts of code:

In wallpaper service:

    @Override
    public void onSurfaceChanged(SurfaceHolder holder, int format, int width,
            int height) {
        super.onSurfaceChanged(holder, format, width, height);
        painting.setSurfaceSize(width, height);
    }

    @Override
    public void onSurfaceCreated(SurfaceHolder holder) {
        super.onSurfaceCreated(holder);
        painting.start();
    }

    @Override
    public void onSurfaceDestroyed(SurfaceHolder holder) {
        boolean retry = true;
        painting.stopPainting();
        while (retry) {
            try {
                painting.join();
                retry = false;
            } catch (InterruptedException e) { }
        }
        super.onSurfaceDestroyed(holder);
    }

In the painting thread:

public void stopPainting() {
    this.run = false;
    synchronized(this) {
        this.notify();
    }
}

public void run() {
    this.run = true;
    Canvas c = null;
    while (run) {
        try {
            synchronized (this) {
                Thread.sleep(50);
                c = this.surfaceHolder.lockCanvas();
                doDraw(c);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            if (c != null) {
                this.surfaceHolder.unlockCanvasAndPost(c); // << -- HERE IS THE PROBLEM
            }
        }
        // if pause...
        synchronized (this) {
            if (wait) {
                try {
                    wait();
                } catch (Exception e) { }
            }
        }
    }
}

Can anyone give me any clue what I'm doing wrong? I'm new both for Java and Android.

Tudor
  • 61,523
  • 12
  • 102
  • 142
Anton Zhuchkov
  • 361
  • 3
  • 9

4 Answers4

2

If error is: UnlockAndPost Failed, it means it unlocked no buffer. After this.surfaceHolder.unlockCanvasAndPost(c);
you can append
this.surfaceHolder.lockCanvas();
(sorry for my poor English proficiency)

virious
  • 571
  • 1
  • 8
  • 27
Doco Dev
  • 692
  • 5
  • 6
2

When you open the preview of wallpaper, creates the object WallpaperService and further creates an instance of Engine. Then the stream starts drawing wallpaper.

Then, when you click "Set wallpaper" - a new instance of WallpaperService is not created. But he call the onCreateEngine() method, which returns another (second) instance of Engine. Which also runs its own thread.

Now you have two competing thread!!! So they lead to an exception being thrown.

All you need to do to fix the bug - is to write a correct method onCreateEngine().

replace this:

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

to this:

private SampleEngine engine;

@Override
public Engine onCreateEngine() {

    if (engine!=null) {
        engine.painting.stopPainting();
        engine = null;
    }
    engine = new SampleEngine();
    return engine;
}
1

I don't see a definitive problem but here are some thoughts.

  • There is a chance that you unlock a canvas that has not been locked. I would set c = null; at the top of your while loop otherwise the previous value of c would be unlocked the next time through the loop.

    while (run) {
        Canvas c = null;
        ...
    
  • Your run field should be marked as volatile because it is accessed by multiple threads.

  • Never call Thread.sleep(...) inside of a synchronized block. That's a very bad practice since it blocks other threads unnecessarily.

  • Make sure you at least log your exceptions. Be extremely careful about catch (Exception e) {}. All that does is mask your problems.

  • There isn't much point in doing the join() inside a while loop. If your thread gets interrupted you should interrupt the painting thread and quit.

  • Since you are both sleeping and waiting, it would make more sense to remove the sleep and do something like:

    try {
       synchronized (this) {
           if (wait) {
              wait();
           else {
              wait(50);
           }
       }
    } catch (Exception e) {
       e.printStackTrace();
    }
    
Gray
  • 115,027
  • 24
  • 293
  • 354
1

I had the same problem with my live wallpaper. On a Nexus 5 emulator it runs fine, but when I run it on a Nexus 10 emulator it crashes the moment the app loads.

I found out that the problem was because the default Skin for the emulator has the wrong resolution. After I changed the Skin to "No Skin" then I don't get the crash anymore.

For more information on how to fix the Skin with wrong resolution, please see: Android Studio - Tablet emulator not showing correct resolution

Community
  • 1
  • 1
PohGuan
  • 51
  • 2