You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android SurfaceView画布不更新及IllegalThreadStateException问题求助

Android纸牌游戏Canvas绘制异常排查与修复

问题描述

  • 开发Android纸牌游戏,其余功能正常,仅Canvas绘制异常:已完成锁定、绘制、解锁提交流程,但屏幕无法更新
  • 初始启动可正常绘制所有卡牌,后续无任何更新;仅在线程抛出IllegalThreadStateException时(如按下Home键/最近应用键切后台)才会更新一次
  • 触摸事件可正常触发游戏类位置变量更新,但drawGame方法无法绘制更新后的内容

相关代码

自定义MainThread代码

public class MainThread extends Thread {
    private final SurfaceHolder surfaceHolder;
    private final GameBoard boardView;
    private boolean running;
    public static Canvas canvas;
    public MainThread(SurfaceHolder surfaceHolder, GameBoard boardView) {
        super();
        this.surfaceHolder = surfaceHolder;
        this.boardView = boardView;
    }
    public void setRunning(boolean isRunning) {
        running = isRunning;
    }
    @Override
    public void run() {
        while (running) {
            canvas = null;
            try {
                canvas = this.surfaceHolder.lockCanvas();
                synchronized(surfaceHolder) {
                    this.boardView.update();
                    this.boardView.draw(canvas);
                }
            } catch (Exception e) {
                System.out.println("CATCH");
            } finally {
                if (canvas != null) {
                    try {
                        surfaceHolder.unlockCanvasAndPost(canvas);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
}

自定义GameBoard(SurfaceView)代码

public class GameBoard extends SurfaceView implements SurfaceHolder.Callback {
    BaseSingleDeckGameObject gameObject;
    private final MainThread thread;

    public GameBoard(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init(attrs, defStyle);
        getHolder().addCallback(this);
        thread = new MainThread(getHolder(), this);
        setFocusable(true);
        gameObject = new KlondikeGameObject(0, context);
    }

    public void update() {
        gameObject.updateGame();
    }

    private void init(AttributeSet attrs, int defStyle) {
        this.setSystemUiVisibility(
                View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
                        | View.SYSTEM_UI_FLAG_LAYOUT_STABLE
                        | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
                        | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
                        | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
                        | View.SYSTEM_UI_FLAG_FULLSCREEN
        );
    }

    public boolean onTouchEvent(MotionEvent motion) {
        gameObject.doTouchEvent(motion);
        return true;
    }

    @Override
    public void draw(Canvas canvas) {
        super.draw(canvas);
        if (canvas != null) {
            gameObject.drawGame(canvas);
        }
    }

    @Override
    public void surfaceCreated(@NonNull SurfaceHolder holder) {
        thread.setRunning(true);
        thread.start();
    }

    @Override
    public void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width, int height) {
    }

    @Override
    public void surfaceDestroyed(@NonNull SurfaceHolder holder) {
        boolean retry = true;
        while (retry) {
            try {
                thread.setRunning(false);
                thread.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            retry = false;
        }
    }
}

MainActivity代码

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle bundle) {
        super.onCreate(bundle);
        this.setContentView(R.layout.game_screen);
        StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder(StrictMode.getVmPolicy())
                .detectLeakedClosableObjects()
                .build());
    }

    @Override
    protected void onResume() {
        super.onResume();
    }
}

报错信息

Exception configuring surface
java.lang.IllegalThreadStateException
    at java.lang.Thread.start(Thread.java:872)
    at com.severinghams.homebrewsolitaire.GameBoard.surfaceCreated(GameBoard.java:113)
    at android.view.SurfaceView.updateSurface(SurfaceView.java:1208)
    at android.view.SurfaceView.setWindowStopped(SurfaceView.java:294)
    at android.view.SurfaceView.surfaceCreated(SurfaceView.java:1699)
    at android.view.ViewRootImpl.notifySurfaceCreated(ViewRootImpl.java:1796)
    at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:2798)
    at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:2008)
    at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:8343)
    at android.view.Choreographer$CallbackRecord.run(Choreographer.java:1136)
    at android.view.Choreographer.doCallbacks(Choreographer.java:958)
    at android.view.Choreographer.doFrame(Choreographer.java:882)
    at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:1121)
    at android.os.Handler.handleCallback(Handler.java:938)
    at android.os.Handler.dispatchMessage(Handler.java:99)
    at android.os.Looper.loop(Looper.java:236)
    at android.app.ActivityThread.main(ActivityThread.java:7904)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:602)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:952)

问题排查与修复方案

1. 核心问题定位

  • 线程重复启动异常:Thread对象只能被启动一次,应用从后台回到前台时surfaceCreated会再次调用,此时尝试启动已运行过的线程会抛出IllegalThreadStateException
  • 渲染循环终止:线程启动失败后,原持续渲染循环停止,仅异常触发时的单次绘制生效,导致屏幕无法持续更新

2. 具体修复步骤

(1)修复线程重复启动问题

修改GameBoard的surfaceCreated方法,判断线程状态后再启动:

@Override
public void surfaceCreated(@NonNull SurfaceHolder holder) {
    // 仅在线程未启动或已终止时启动,避免重复start
    if (thread.getState() == Thread.State.NEW || !thread.isAlive()) {
        thread.setRunning(true);
        thread.start();
    } else {
        // 线程已存活时仅恢复运行状态
        thread.setRunning(true);
    }
}

优化surfaceDestroyed方法,确保线程正确终止:

@Override
public void surfaceDestroyed(@NonNull SurfaceHolder holder) {
    boolean retry = true;
    thread.setRunning(false);
    while (retry) {
        try {
            thread.join();
            retry = false;
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

(2)修复Surface绘制干扰问题

移除GameBoard.draw中的super.draw(canvas),因为SurfaceView的父类draw会将内容绘制到View缓存而非Surface上,导致绘制内容无法显示:

@Override
public void draw(Canvas canvas) {
    if (canvas != null) {
        gameObject.drawGame(canvas);
    }
}

(3)优化渲染循环(可选)

在MainThread中添加帧率控制,避免过度绘制:

@Override
public void run() {
    long lastTime = System.nanoTime();
    final double nsPerFrame = 1000000000.0 / 60.0; // 60帧/秒
    double delta = 0;
    while (running) {
        long now = System.nanoTime();
        delta += (now - lastTime) / nsPerFrame;
        lastTime = now;
        
        while (delta >= 1) {
            canvas = null;
            try {
                canvas = surfaceHolder.lockCanvas();
                synchronized(surfaceHolder) {
                    boardView.update();
                    boardView.draw(canvas);
                }
                delta--;
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                if (canvas != null) {
                    try {
                        surfaceHolder.unlockCanvasAndPost(canvas);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
}

3. 额外注意事项

  • 确保gameObject.drawGame每次绘制时先覆盖整个画布(比如绘制背景),避免残留旧内容
  • 触摸事件中修改的游戏状态变量需保证线程安全,可使用volatile修饰或在同步块中访问

内容的提问来源于stack exchange,提问作者SeveringHams

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.24 16:13:15