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() }
其他潜在问题修复
- 避免超大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) }
移除冗余绘制代码
删除onDrawLine()和onTouchEventLine()中多余的canvas?.drawBitmap()调用,onDraw()已经统一负责绘制extraBitmap。修复触摸事件的坐标计算错误
onTouchEventSmoothLine()中dx和dy的计算逻辑错误,修正为:
val dx = abs(motionTouchEventX - currentX) val dy = abs(motionTouchEventY - currentY)
内容的提问来源于stack exchange,提问作者Ireaperz
相关产品推荐
相关产品推荐

