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
相关产品推荐
相关产品推荐

