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.