Android SurfaceView绘制椭圆时释放手指触发Surface已释放异常
SurfaceView释放时崩溃的解决方法
问题原因
当手指抬起调用layout.removeView(ovalsView)时,SurfaceView的Surface会被销毁,但后台绘制线程仍在尝试执行unlockCanvasAndPost(canvas)操作,此时Surface已被释放,触发IllegalStateException。另外原代码仅通过Thread.interrupted()控制线程停止,这种方式不够可靠,线程可能在Surface销毁后仍继续执行绘制逻辑。
修复方案
1. 添加线程运行控制标志
在OvalsView中添加volatile修饰的布尔变量,保证多线程下的可见性,用于可靠控制绘制线程启停。
2. 完善线程停止逻辑
在surfaceDestroyed中先标记线程停止,再等待线程完全结束,避免Surface销毁后线程仍在运行。
3. 修正循环停止条件
将绘制线程的循环条件结合标志位和线程中断状态,确保线程能及时停止。
4. 优化View实例管理
避免重复添加同一SurfaceView实例到布局,每次按下创建新实例,释放后置空。
修改后的完整代码
package com.example.lab10 import android.annotation.SuppressLint import androidx.appcompat.app.AppCompatActivity import android.os.Bundle import android.content.Context import android.graphics.Canvas import android.graphics.Color import android.graphics.Paint import android.util.DisplayMetrics import android.util.Log import android.view.MotionEvent import android.view.SurfaceHolder import android.view.SurfaceView import android.view.View import android.view.WindowManager import androidx.constraintlayout.widget.ConstraintLayout class TaskSix : AppCompatActivity() { @SuppressLint("ClickableViewAccessibility") override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_task_six) title = "Task#6" val layout = findViewById<ConstraintLayout>(R.id.layout) var ovalsView: OvalsView? = null layout.setOnTouchListener(View.OnTouchListener { view, motionEvent -> when (motionEvent.actionMasked) { MotionEvent.ACTION_DOWN, MotionEvent.ACTION_POINTER_DOWN -> { Log.d("qqq", "down") val ID = motionEvent.getPointerId(motionEvent.actionIndex) Log.d("qqq", "element with id: $ID") if (ovalsView == null) { ovalsView = OvalsView(this) layout.addView(ovalsView) } } MotionEvent.ACTION_MOVE -> { Log.d("qqq", "move") var idx = 0 while (idx < motionEvent.pointerCount) { val ID = motionEvent.getPointerId(idx) idx++ ovalsView?.let { it._top = motionEvent.y it._left = motionEvent.x } } } MotionEvent.ACTION_UP, MotionEvent.ACTION_POINTER_UP -> { Log.d("qqq", "up") ovalsView?.let { layout.removeView(it) ovalsView = null } } else -> Log.d("qqq", "unhandled") } return@OnTouchListener true }) } private class OvalsView(context: Context) : SurfaceView(context), SurfaceHolder.Callback { private val mSurfaceHolder: SurfaceHolder private val mPainter = Paint() private var mDrawingThread: Thread? = null private val mDisplay = DisplayMetrics() private var mDisplayWidth: Int private var mDisplayHeight: Int private var mRotation = 0f @Volatile private var isRunning = false var _top: Float = 0f var _left: Float = 0f init { val wm = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager wm.defaultDisplay.getMetrics(mDisplay) mDisplayWidth = mDisplay.widthPixels mDisplayHeight = mDisplay.heightPixels mPainter.isAntiAlias = true mPainter.color = Color.RED mSurfaceHolder = holder mSurfaceHolder.addCallback(this) } private fun animateOvals(): Boolean { mRotation += 1 return true } private fun drawWheel(canvas: Canvas) { canvas.drawColor(Color.WHITE) if (_top > 0f && _left > 0f) { canvas.drawOval(_left, _top, _left + 200f, _top + 350f, mPainter) } } override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { mDisplayWidth = w mDisplayHeight = h super.onSizeChanged(w, h, oldw, oldh) } override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {} override fun surfaceDestroyed(holder: SurfaceHolder) { isRunning = false mDrawingThread?.let { thread -> try { thread.join() } catch (e: InterruptedException) { Log.e("OvalsView", "线程等待中断", e) } mDrawingThread = null } } override fun surfaceCreated(holder: SurfaceHolder) { isRunning = true mDrawingThread = Thread(Runnable { val MAX_FRAME_TIME = 1000 / 60 var frameStartTime: Long var frameTime: Long var canvas: Canvas? = null while (isRunning && !Thread.currentThread().isInterrupted && animateOvals()) { frameStartTime = System.nanoTime() try { canvas = mSurfaceHolder.lockCanvas() if (canvas != null && isRunning) { drawWheel(canvas) mSurfaceHolder.unlockCanvasAndPost(canvas) } } catch (e: IllegalStateException) { Log.e("OvalsView", "绘制异常", e) } finally { canvas?.let { try { mSurfaceHolder.unlockCanvasAndPost(it) } catch (ignored: IllegalStateException) {} } } frameTime = (System.nanoTime() - frameStartTime) / 1000000 if (frameTime < MAX_FRAME_TIME) { try { Thread.sleep(MAX_FRAME_TIME - frameTime) } catch (e: InterruptedException) { Thread.currentThread().interrupt() } } } }) mDrawingThread?.start() } } }
关键改动说明
- 线程控制标志:
@Volatile private var isRunning = false确保主线程修改后,绘制线程能立即感知到状态变化。 - surfaceDestroyed优化:先标记线程停止,再调用
thread.join()等待线程完全结束,彻底避免Surface销毁后线程仍执行绘制操作。 - 循环条件修正:结合
isRunning和线程中断状态,保证线程能及时响应停止指令。 - 异常捕获:添加
IllegalStateException捕获,即使线程未及时停止,也能避免崩溃。 - View实例管理:每次按下创建新的OvalsView实例,释放后置空,避免重复添加同一View导致的布局异常。
内容的提问来源于stack exchange,提问作者np.
相关产品推荐
相关产品推荐

