Search code examples
javaandroidmultithreadingsurfaceview

Android drawing bitmap multiple times and then onScroll causes ANR


I want to do canvas.drawBitmap(...) multiple times but now app freezes (ANR) once I move my SurfaceView (call surfaceView.setX(gs.moveX); surfaceView.setY(gs.moveY); in onScroll(...)) in my SurfaceView class with custom GestureDetector. This SurfaceView flow is controlled by GameThread in which I draw 3 times canvas.drawBitmap(...). What is worth noticing is that when I comment out second or third canvas.drawBitmap(...) from drawTmxMaps(...) then app scrolls almost correctly - almost because it works but scrolling is laggy.

So how to solve my problem?

Here is my code.

Loader.java

private static Map<Integer, Bitmap[]> differentFramesMap = new HashMap<>();
private static Map<Integer, Canvas[]> differentCanvasesMap = new HashMap<>();

(...)

public static void drawTmxMaps(Canvas canvas, Context context) {
    canvas.drawBitmap(differentFramesMap.get(0)[framesFirstAnimIter], 0, 0, PaintTemplates.getInstance(context).pMap);
    canvas.drawBitmap(differentFramesMap.get(1)[framesSecondAnimIter], 0, 0, PaintTemplates.getInstance(context).pMap);
    canvas.drawBitmap(differentFramesMap.get(2)[framesThirdAnimIter], 0, 0, PaintTemplates.getInstance(context).pMap);

    if (!isIterStarted) {
        iterTmxFrames();
        isIterStarted = true;
    }
}

private static void iterTmxFrames() {
    runnable = () -> {
        framesFirstAnimIter++;
        framesSecondAnimIter++;
        framesThirdAnimIter++;
        if (framesFirstAnimIter == 3)
            framesFirstAnimIter = 0;
        if (framesSecondAnimIter == 2)
            framesSecondAnimIter = 0;
        if (framesThirdAnimIter == 2)
            framesThirdAnimIter = 0;
        handler.postDelayed(runnable, 1000);
    };
    handler.postDelayed(runnable, 1000);
}

MyThread.java

    @Override
public void run() {
    while (isRunning) {
        startTime = SystemClock.uptimeMillis();
        Canvas canvas = surfaceHolder.lockCanvas(null);
        if (canvas != null) {
            synchronized (surfaceHolder) {

                TmxLoader.drawTmxMaps(canvas, gameSurface.getContext());

                surfaceHolder.unlockCanvasAndPost(canvas);
            }
        }

        loopTime = SystemClock.uptimeMillis() - startTime;

        if (loopTime < delay) {
            try {
                Thread.sleep(delay - loopTime);
            } catch (InterruptedException e) {
                Log.e("Interupted ex", e.getMessage());
            }
        }
    }
}

EDIT after answer

Now there is no ANR but the surface seems not to refresh because thre are old positions of canvas on the screen. It looks like this:

enter image description here

Solved by simply adding canvas.drawARGB(255, 0, 0, 0); as a first method in GameThread run().


Solution

  • Your code should look like this:

    Loader.java:

    public class Loader {
    private static Map<Integer, Bitmap[]> differentFramesMap = new HashMap<>();
    private static Map<Integer, Canvas[]> differentCanvasesMap = new HashMap<>();
    private static boolean isIterStarted = false;
    private static int framesFirstAnimIter = 0;
    private static int framesSecondAnimIter = 0;
    private static int framesThirdAnimIter = 0;
    private static Handler handler = new Handler(Looper.getMainLooper());
    private static Runnable runnable;
    
    public static void drawTmxMaps(Canvas canvas, Context context) {
        canvas.drawBitmap(differentFramesMap.get(0)[framesFirstAnimIter], 0, 0, PaintTemplates.getInstance(context).pMap);
        canvas.drawBitmap(differentFramesMap.get(1)[framesSecondAnimIter], 0, 0, PaintTemplates.getInstance(context).pMap);
        canvas.drawBitmap(differentFramesMap.get(2)[framesThirdAnimIter], 0, 0, PaintTemplates.getInstance(context).pMap);
    
        if (!isIterStarted) {
            iterTmxFrames();
            isIterStarted = true;
        }
    }
    
    private static void iterTmxFrames() {
        runnable = new Runnable() {
            @Override
            public void run() {
                framesFirstAnimIter++;
                framesSecondAnimIter++;
                framesThirdAnimIter++;
                if (framesFirstAnimIter == 3)
                    framesFirstAnimIter = 0;
                if (framesSecondAnimIter == 2)
                    framesSecondAnimIter = 0;
                if (framesThirdAnimIter == 2)
                    framesThirdAnimIter = 0;
                handler.postDelayed(runnable, 1000);
            }
        };
        handler.postDelayed(runnable, 1000);
    }
    }
    

    MyThread.java:

    public class MyThread extends Thread {
    private final SurfaceHolder surfaceHolder;
    private final GameSurface gameSurface;
    private boolean isRunning;
    private long delay = 33; // 30 FPS
    private long startTime;
    private long loopTime;
    private float scrollX;
    private float scrollY;
    
    public MyThread(SurfaceHolder surfaceHolder, GameSurface gameSurface) {
        this.surfaceHolder = surfaceHolder;
        this.gameSurface = gameSurface;
    }
    
    public void setRunning(boolean running) {
        isRunning = running;
    }
    
    public void setScroll(float scrollX, float scrollY) {
        this.scrollX = scrollX;
        this.scrollY = scrollY;
    }
    
    @Override
    public void run() {
        while (isRunning) {
            startTime = SystemClock.uptimeMillis();
            Canvas canvas = surfaceHolder.lockCanvas(null);
            if (canvas != null) {
                synchronized (surfaceHolder) {
                    canvas.translate(-scrollX, -scrollY);
                    Loader.drawTmxMaps(canvas, gameSurface.getContext());
                    surfaceHolder.unlockCanvasAndPost(canvas);
                }
            }
            loopTime = SystemClock.uptimeMillis() - startTime;
            if (loopTime < delay) {
                try {
                    Thread.sleep(delay - loopTime);
                } catch (InterruptedException e) {
                    Log.e("Interrupted ex", e.getMessage());
                }
            }
        }
    }
    }