Search code examples
androidandroid-canvassurfaceviewanimated-gif

Android SufraceView - unable to update Canvas content


I developing a wallpaper with a GIFs using SurfaceView and Canvas. By the time GIFs has to be updated and changed. But the problem is when time comes to update screen goes to black.

Here's fragment from WallpaperEngine

class WallpaperEngine extends Engine implements SharedPreferences.OnSharedPreferenceChangeListener {

private static final String TAG = "WallpaperEngine";
    private AnimationThread animationThread;
    private SurfaceHolder mSurfaceHolder;
    private Scene scene;
    private Movie movie1;
    private Movie movie2;
    private Movie movie3;
    private Movie movie4;
    private Movie movie5;
    private Bitmap bitmap1;
    private Bitmap bitmap2;
    private Bitmap bitmap3;
    private Bitmap bitmap4;
    private BitmapFactory.Options opts, bitmapOptions;
    private SharedPreferences prefs;
    private float dx = 0.0f;
    private int mWidth, mHeight;

    WallpaperManager myWallpaperManager;
    View view;
    WindowManager.LayoutParams lp;

    @SuppressLint("NewApi")
    @Override
    public void onCreate(SurfaceHolder surfaceHolder) {
        super.onCreate(surfaceHolder);

        Log.d(TAG, "onCreate");

        mSurfaceHolder = surfaceHolder;

        opts = new BitmapFactory.Options();
        opts.inSampleSize = 2;

        bitmapOptions = new BitmapFactory.Options();
        bitmapOptions.inSampleSize = 1;

        myWallpaperManager = WallpaperManager.getInstance(getApplicationContext());

        prefs = PreferenceManager.getDefaultSharedPreferences(Wallpaper.this);

        prefs.registerOnSharedPreferenceChangeListener(this);

        checkCurrentTime();

        movie1 = Movie.decodeStream(getResources().openRawResource(R.raw.fountain));
        movie2 = Movie.decodeStream(getResources().openRawResource(R.raw.idle_cycle));
        movie3 = Movie.decodeStream(getResources().openRawResource(R.raw.tree_move));
        movie4 = Movie.decodeStream(getResources().openRawResource(R.raw.wash_full));
        movie5 = Movie.decodeStream(getResources().openRawResource(R.raw.prayer_full));

        DisplayMetrics displayMetrics = new DisplayMetrics();
        Display display = ((WindowManager) getBaseContext().getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
        display.getMetrics(displayMetrics);

        mHeight = displayMetrics.heightPixels;
        mWidth  = displayMetrics.widthPixels;

        // create the scene
        scene = new Scene(sky, ground, town, middle, movie1, movie2, movie3, null, null, mHeight);

        // start animation thread; thread starts paused
        // will run onVisibilityChanged
        animationThread = new AnimationThread(mSurfaceHolder, scene);
        animationThread.start();
    }

When time comes to change GIF animations, I call updateWallpapers() method:

private void updateWallpapers(int skyId, int townId, int middleId, Movie movie2, Movie movie4, Movie movie5) {

        sky    = BitmapFactory.decodeResource(getResources(), skyId);
        ground = BitmapFactory.decodeResource(getResources(), R.drawable.ground_plainblack, opts);
        town   = BitmapFactory.decodeResource(getResources(), townId);
        middle = BitmapFactory.decodeResource(getResources(), middleId);

        if (movie2 != null) {
            scene = new Scene(sky, ground, town, middle, movie1, movie2, movie3, null, null, mHeight);

        } else if (movie4 != null) {
            scene = new Scene(sky, ground, town, middle, movie1, null, movie3, movie4, null, mHeight);

        } else if (movie5 != null) {
            scene = new Scene(sky, ground, town, middle, movie1, null, movie3, null, movie5, mHeight);
        }
        animationThread.stopThread();
        joinThread(animationThread);
        animationThread = null;
        animationThread = new AnimationThread(mSurfaceHolder, scene);
        animationThread.start();
        animationThread.resumeThread();
    }

AnimationThread class, I think here is the problem, but I can't to solve it:

public class AnimationThread extends Thread {

private static final String TAG = "AnimationThread";

private Object pauseLock = new Object();

private boolean running = true;
private boolean paused = true;

private int fps = 30;
private int timeFrame = 1000 / fps; // drawing time frame in miliseconds 1000 ms / fps

final private SurfaceHolder surfaceHolder;
private Scene scene;

public AnimationThread(SurfaceHolder surfaceHolder, Scene scene) {
    this.surfaceHolder = surfaceHolder;
    this.scene = scene;
}

@Override
public void run() {

    while (running) {

        waitOnPause();

        if (!running) {
            return;
        }

        long beforeDrawTime = System.currentTimeMillis();

        Canvas canvas = null;
        try {

            if (surfaceHolder.getSurface().isValid()) {
                canvas = surfaceHolder.lockCanvas();
               /** Clean Canvas before draw. 
                   When I erase this line screen not changing and it draws GIFs above each other, layer by layer. 
                   But now when I update GIFs screen just turns black and freezes on this state.  
                   HERE'S THE PROBLEM!!! */
                canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.MULTIPLY);
            }
            /** Workaround for: SurfaceTextureClient: dequeueBuffer failed (No such device) */
            if (canvas == null) {
                continue;
            }
            synchronized (surfaceHolder) {
                scene.draw(canvas);
            }

        } catch (IllegalArgumentException e) {
            Log.e(TAG, "Error during surfaceHolder.lockCanvas()", e);
            stopThread();
        } finally {
            if (canvas != null) {
                try {
                    surfaceHolder.unlockCanvasAndPost(canvas);
                } catch (IllegalArgumentException e) {
                    Log.e(TAG, "Error during unlockCanvasAndPost()", e);
                    stopThread();
                }
            }
        }

        long afterDrawTime = System.currentTimeMillis() - beforeDrawTime;
        try {
            if (timeFrame > afterDrawTime) {
                Thread.sleep(timeFrame - afterDrawTime);
            }
        } catch (InterruptedException ex) {
            Log.e(TAG, "Exception during Thread.sleep().", ex);
        }

    }

}

public void stopThread() {
    synchronized (pauseLock) {
        paused = false;
        running = false;
        pauseLock.notifyAll();
    }
    Log.d(TAG, "Stopped thread (" + this.getId() + ")");
}

public void pauseThread() {
    synchronized (pauseLock) {
        paused = true;
    }
    Log.d(TAG, "Paused thread (" + this.getId() + ")");
}

public void resumeThread() {
    synchronized (pauseLock) {
        paused = false;
        pauseLock.notifyAll();
    }
    Log.d(TAG, "Resumed thread (" + this.getId() + ")");
}

private void waitOnPause() {
    synchronized (pauseLock) {
        while (paused) {
            try {
                pauseLock.wait();
            } catch (InterruptedException e) {
            }
        }
    }
}

}

Scene class:

public class Scene {

private Bitmap background = null;
private Bitmap ground = null;
private Bitmap town = null;
private Bitmap middle = null;
private Movie movie1 = null;
private Movie movie2 = null;
private Movie movie3 = null;
private Movie movie4 = null;
private Movie movie5 = null;
private Rect rectangleFrame = null;
private int height;
private int width;
private float dx;

private int centerX;
private int centerY;

public Scene(Bitmap backgroundImg, Bitmap groundImg, Bitmap townImg, Bitmap middleImg,
             Movie movie1, Movie movie2, Movie movie3 Movie movie4,
             Movie movie5, int screenHeight) {

    background  = backgroundImg;
    town        = townImg;
    middle      = middleImg;
    ground      = groundImg;
    height      = screenHeight;
    this.movie1 = movie1;
    this.movie2 = movie2;
    this.movie3 = movie3;
    this.movie3 = movie4;
    this.movie4 = movie5;
}

public synchronized void updateSize(int width, int height, float dx) {

    this.dx = dx;
    centerY = height / 2;

    this.height = height;
    this.width = width;
}

public synchronized void draw(Canvas canvas) {

    canvas.save();
    canvas.translate(dx, 0);

    rectangleFrame = new Rect(0, 0, (int) (this.width * 2.5), this.height);
    centerX = rectangleFrame.centerX() / 2;

    canvas.drawBitmap(background, null, rectangleFrame, null);

    canvas.drawBitmap(town, null, rectangleFrame, null);

    canvas.drawBitmap(middle, null, rectangleFrame, null);

    canvas.drawBitmap(ground, null, rectangleFrame, null);

    movie1.draw(canvas, centerX - (movie3.width() / 2),
            (rectangleFrame.centerY() + (rectangleFrame.centerY() / 1.65f)) - this.movie1.height());

    if (movie2 != null) {
        movie2.draw(canvas, centerX,
                (rectangleFrame.centerY() + (rectangleFrame.centerY() / 1.6f)) - movie2.height());
    }
    if (movie4 != null) {
        movie4.draw(canvas, centerX - (movie3.width() / 2 - this.movie1.width() + 30),
                (rectangleFrame.centerY() + (rectangleFrame.centerY() / 1.6f)) - movie4.height());
    }
    if (movie5 != null) {
        movie5.draw(canvas, centerX,
                (rectangleFrame.centerY() + (rectangleFrame.centerY() / 1.6f)) - movie5.height());
    }

    movie3.draw(canvas, centerX + this.movie1.width(),
            (rectangleFrame.centerY() + (rectangleFrame.centerY() / 1.6f)) - movie3.height());

    canvas.restore();

    this.movie1.setTime((int) (System.currentTimeMillis() % this.movie1.duration()));

    if (movie2 != null) {
        movie2.setTime((int) (System.currentTimeMillis() % movie2.duration()));
    }
    if (movie4 != null) {
        movie4.setTime((int) (System.currentTimeMillis() % movie4.duration()));
    }
    if (movie5 != null) {
        movie5.setTime((int) (System.currentTimeMillis() % movie5.duration()));
    }
    movie3.setTime((int) (System.currentTimeMillis() % movie3.duration()));

}

Any help will be appreciated! Thanks!


Solution

  • After spending lot of time on my code I've finally found the solution! I just erased this line canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.MULTIPLY); and add updateSize() method after AnimationThread implementation, like this:

    animationThread.stopThread();
    joinThread(animationThread);
    animationThread = null;
    animationThread = new AnimationThread(mSurfaceHolder, scene);
    animationThread.start();
    animationThread.resumeThread();
    /**----------------Here!!!---------------*/
    scene.updateSize(mWidth, mHeight, dx);
    

    Now it works fine!