Android рисует растровое изображение несколько раз, а затем onScroll вызывает ANR

Я хочу сделать canvas.drawBitmap(...) несколько раз, но теперь приложение зависает (ANR), как только я перемещаю свой SurfaceView (вызов surfaceView.setX(gs.moveX); surfaceView.setY(gs.moveY); в onScroll(...)) в моем классе SurfaceView с пользовательским GestureDetector. Этот поток SurfaceView контролируется GameThread, в котором я рисую 3 раза canvas.drawBitmap(...). Стоит отметить, что когда я комментирую второй или третий canvas.drawBitmap(...) из drawTmxMaps(...), приложение прокручивается почти правильно — почти потому, что оно работает, но прокрутка лагает.

Так как решить мою проблему?

Вот мой код.

Загрузчик.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());
            }
        }
    }
}

РЕДАКТИРОВАТЬ после ответа

Теперь нет ANR, но поверхность, кажется, не обновляется, потому что на экране есть старые позиции холста. Это выглядит так:

Решается простым добавлением canvas.drawARGB(255, 0, 0, 0); в качестве первого метода в GameThread run().

Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
0
0
67
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Похоже, ваш класс MyThread рисует все в основном потоке пользовательского интерфейса. В результате пользовательский интерфейс зависает из-за дополнительной работы.

Лучший способ избежать этого — создать «Runnable», который выполняет рисование и пересылает его «Handler» в своем собственном потоке.

Вот как вы можете изменить свой код, чтобы добиться этого:

Загрузчик.java

    public static void drawTmxMaps(Canvas canvas, Context context) {
    Canvas[] canvases = differentCanvasesMap.get(0);
    Bitmap[] bitmaps = differentFramesMap.get(0);
    for (int i = 0; i < canvases.length; i++) {
        canvases[i].drawBitmap(bitmaps[i], 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;
            }

            // Post the drawing to a handler on a separate thread
            handler.post(new Runnable() {
                @Override
                public void run() {
                    Canvas[] canvases = differentCanvasesMap.get(0);
                    Bitmap[] bitmaps = differentFramesMap.get(0);
                    for (int i = 0; i < canvases.length; i++) {
                        canvases[i].drawBitmap(bitmaps[i], 0, 0, null);
                    }
                }
            });

            handler.postDelayed(runnable, 1000);
        }
    };
    handler.postDelayed(runnable, 1000);
}

В методе drawTmxMaps() создается цикл для перебора каждого холста и растрового изображения и отрисовки их всех одновременно.

В методе «iterTmxFrames()» мы используем «Handler» для публикации «Runnable», где он разделяет потоки каждые 1000 мс.

«Runnable» выполняет фактическое рисование на холсте, используя растровые изображения из «DifferentFramesMap».

В классе «MyThread» вызовите «Loader.drawTmxMaps()», чтобы нарисовать растровые изображения. После этого «surfaceHolder.unlockCanvasAndPost(canvas)» можно использовать для публикации полученных объектов «Canvas» в «SurfaceHolder».

После этого «SurfaceView» больше не должен зависать или отставать при прокрутке.

На самом деле SurfaceView следует перенести из основного потока пользовательского интерфейса в отдельный поток.

  1. Создайте новый поток для обработки движения SurfaceView.

    частный класс MoveSurfaceViewThread расширяет поток { закрытый окончательный SurfaceView SurfaceView; приватный финальный поплавок moveX; приватный финальный поплавок moveY;

    public MoveSurfaceViewThread (SurfaceView surfaceView, float moveX, float moveY) { this.surfaceView = SurfaceView; это.переместитьX = переместитьX; this.moveY = двигатьсяY; }

    @Override публичный недействительный запуск () { поверхностьView.setX(переместитьX); SurfaceView.setY(переместитьY); } }

  2. В своем пользовательском GestureDetector создайте экземпляр MoveSurfaceViewThread и запустить его при прокрутке

     public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, 
     float distanceY) {
     float moveX = gameSurface.getX() - distanceX;
     float moveY = gameSurface.getY() - distanceY;
    
     // Create a new thread to move the surfaceView
     MoveSurfaceViewThread moveSurfaceViewThread = new 
     MoveSurfaceViewThread(gameSurface, moveX, moveY);
     moveSurfaceViewThread.start();
     return true;
     }
    

Помещение SurfaceView в отдельный поток сделает приложение более отзывчивым и предотвратит блокировку потока пользовательского интерфейса.

Спасибо за ваш ответ, но ваш код вообще не рисует эти растровые изображения [] :(. Ни одно из растровых изображений не рисуется

mnietuniema 18.04.2023 20:10

Может быть, мне также следует что-то изменить в MyThread?

mnietuniema 18.04.2023 22:17

Надеюсь, отредактированный ответ поможет вам

Harshavardhan 20.04.2023 17:43

так здорово, так что вы помните меня :). Завтра протестирую, но напишите, как именно должен выглядеть MyThread?

mnietuniema 20.04.2023 22:40
Ответ принят как подходящий

Ваш код должен выглядеть так:

Загрузчик.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());
            }
        }
    }
}
}

Спасибо, есть прогресс, хе-хе, ANR нет, но я добавил свой текущий скриншот, когда я перемещаю холст, что тогда происходит.

mnietuniema 22.04.2023 12:55

Другие вопросы по теме