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

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.

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.05 09:15:31