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

Android应用调用restoreStacks()时闪退无日志,添加drawBitmap后触发问题

问题描述

Android应用调用restoreStacks()方法时直接闪退,但Android Studio控制台及日志无任何错误输出,问题出在添加canvas?.drawBitmap(extraBitmap, 0f,0f, null)代码后。

原代码

class MyCanvasView(context: Context, attrs: AttributeSet?): View(context, attrs) {

    companion object {
        const val SmoothLine = 1
        const val LINE = 2
        const val RECTANGLE = 3
        const val SQUARE = 4
        const val CIRCLE = 5
        const val TRIANGLE = 6
    }

    private var isDrawing = false


    var mCurrentShape = 1
    private val paint = Paint()
    private val newPaint = Paint()


    private var scaleFactor = 1.0f


    private var motionTouchEventX = 0f
    private var motionTouchEventY = 0f
    private var currentX = 0f
    private var currentY = 0f
    private val touchTolerance = ViewConfiguration.get(context).scaledTouchSlop


    private val path = Path()

    private val stackPaths = Stack<Path>()
    private val stackPaint = Stack<Paint>()


    private lateinit var extraCanvas: Canvas
    private var canvas: Canvas? = null
    private lateinit var extraBitmap: Bitmap
    private val backgroundColor = ResourcesCompat.getColor(resources, R.color.white, null)


    public override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
        super.onSizeChanged(w, h, oldw, oldh)
        extraBitmap = Bitmap.createBitmap(width*3+300, height*3+500, Bitmap.Config.ARGB_8888)
        extraCanvas = Canvas(extraBitmap)
        extraCanvas.drawColor(backgroundColor)

    }

    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        this.canvas = canvas
        canvas.drawBitmap(extraBitmap, 0f, 0f, null)
        when (mCurrentShape) {
            LINE -> onDrawLine(canvas)
            RECTANGLE -> onDrawRectangle(canvas)
            SQUARE -> onDrawSquare(canvas)
            CIRCLE -> onDrawCircle(canvas)
            TRIANGLE -> onDrawTriangle(canvas)
        }
    }

    override fun onTouchEvent(event: MotionEvent): Boolean {
        motionTouchEventX = event.x * (1 / scaleFactor)
        motionTouchEventY = event.y * (1 / scaleFactor)

        when (mCurrentShape) {
                    LINE -> onTouchEventLine(event)
                    SmoothLine -> onTouchEventSmoothLine(event)
                    RECTANGLE -> onTouchEventRectangle(event)
                    SQUARE -> onTouchEventSquare(event)
                    CIRCLE -> onTouchEventCircle(event)
                    TRIANGLE -> onTouchEventTriangle(event)
                }
        return true
    }

    //------------------------------------------------------------------
    // Line
    //------------------------------------------------------------------
    private fun onDrawLine(canvas: Canvas){
        val dx = abs(motionTouchEventX - currentX)
        val dy = abs(motionTouchEventY - currentY)
        if (dx >= touchTolerance|| dy >= touchTolerance) {
            canvas.drawLine(currentX, currentY, motionTouchEventX, motionTouchEventY, newPaint)
        }
        if(!isDrawing) this.canvas?.drawBitmap(extraBitmap, 0f, 0f, null)
    }
    private fun onTouchEventLine(event: MotionEvent){
        when (event.action) {
            MotionEvent.ACTION_DOWN -> {
                isDrawing = true
                currentX = motionTouchEventX
                currentY = motionTouchEventY
                path.reset()
                path.moveTo(motionTouchEventX, motionTouchEventY)
            }
            MotionEvent.ACTION_MOVE -> invalidate()
            MotionEvent.ACTION_UP -> {
                path.quadTo(currentX, currentY, motionTouchEventX, motionTouchEventY)
                extraCanvas.drawPath(path, paint)
                addToStack(path)
                path.reset()
                isDrawing = false
                canvas?.drawBitmap(extraBitmap, 0f,0f, null)
            }
        }
    }
    //------------------------------------------------------------------
    // SmoothLine
    //------------------------------------------------------------------
    private fun onTouchEventSmoothLine(event: MotionEvent) {
        when (event.action) {
            MotionEvent.ACTION_DOWN -> {
                currentX = motionTouchEventX
                currentY = motionTouchEventY
                path.reset()
                path.moveTo(motionTouchEventX, motionTouchEventY)
            }
            MotionEvent.ACTION_MOVE -> {
                val dx = abs(motionTouchEventY - currentX)
                val dy = abs(motionTouchEventY - currentX)
                if (dx >= touchTolerance || dy >= touchTolerance) {
                    path.quadTo(currentX, currentY, (motionTouchEventX + currentX) / 2, (motionTouchEventY + currentY) / 2)
                    currentX = motionTouchEventX
                    currentY = motionTouchEventY
                }
                extraCanvas.drawPath(path, paint)
            }
            MotionEvent.ACTION_UP -> {
                addToStack(path)
                path.reset()
            }
        }
        invalidate()
    }

    private fun addToStack(path: Path){
        stackPaths.push(Path(path))
        stackPaint.push(Paint(paint))
    }
    private fun restoreStacks(){
        extraBitmap.eraseColor(Color.TRANSPARENT)
        stackPaths.forEachIndexed { index, path ->
            extraCanvas.drawPath(path, stackPaint[index])
        }
        canvas?.drawBitmap(extraBitmap, 0f,0f, null)
        invalidate()
    }

    fun undoLastAction() {
        if (stackPaths.isNotEmpty()) {
            stackPaths.pop()
            stackPaint.pop()
        }
        restoreStacks()

    }

    override fun onDetachedFromWindow() {
        extraBitmap.recycle()
        super.onDetachedFromWindow()
    }
}
问题分析与修复方案

核心闪退原因

restoreStacks()中直接调用canvas?.drawBitmap(...)是错误操作:

  • canvas变量仅在onDraw()方法执行时被赋值,其他时机可能为null;
  • Android的View绘制必须由系统调度,在onDraw()流程外直接操作Canvas会触发线程安全问题,导致无日志闪退。

关键修复

修改restoreStacks()方法,移除直接操作Canvas的代码,改为调用invalidate()让系统触发onDraw()更新视图:

private fun restoreStacks(){
    // 用背景色清空画布,避免透明背景
    extraCanvas.drawColor(backgroundColor)
    stackPaths.forEachIndexed { index, path ->
        extraCanvas.drawPath(path, stackPaint[index])
    }
    // 交给系统触发绘制流程,不要手动操作Canvas
    invalidate()
}

其他潜在问题修复

  1. 避免超大Bitmap导致OOM
    extraBitmap原尺寸width*3+300极易超出内存限制,改为使用View实际宽高:
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
    super.onSizeChanged(w, h, oldw, oldh)
    // 回收旧Bitmap避免内存泄漏
    if (::extraBitmap.isInitialized) {
        extraBitmap.recycle()
    }
    // 使用View当前宽高创建Bitmap
    extraBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888)
    extraCanvas = Canvas(extraBitmap)
    extraCanvas.drawColor(backgroundColor)
}
  1. 移除冗余绘制代码
    删除onDrawLine()和onTouchEventLine()中多余的canvas?.drawBitmap()调用,onDraw()已经统一负责绘制extraBitmap。

  2. 修复触摸事件的坐标计算错误
    onTouchEventSmoothLine()中dx和dy的计算逻辑错误,修正为:

val dx = abs(motionTouchEventX - currentX)
val dy = abs(motionTouchEventY - currentY)

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.27 12:25:08