[修复] Bitmap 未正确回收导致的闪退问题

This commit is contained in:
HapeLee
2026-03-10 00:12:26 +08:00
parent 30ba19e00d
commit b8003e9175
4 changed files with 34 additions and 26 deletions
@@ -70,12 +70,9 @@ object ImageProvider {
removeCount++ removeCount++
} }
} }
//错误图片不能释放,占位用,防止一直重复获取图片 // 移除 oldValue.recycle()。
if (oldValue != errorBitmap) { // 在 Android 8.0+ 像素内存由 GC 自动管理,手动回收容易导致 Canvas 绘制时崩溃。
oldValue.recycle() // 特别是在阅读页返回书架时,DisplayList 可能仍持有 Bitmap 引用。
//putDebug("ImageProvider: trigger bitmap recycle. URI: $filePath")
//putDebug("ImageProvider : cacheUsage ${size()}bytes / ${maxSize()}bytes")
}
} }
} }
@@ -86,7 +83,12 @@ object ImageProvider {
} }
fun get(key: String): Bitmap? { fun get(key: String): Bitmap? {
return bitmapLruCache[key] val bitmap = bitmapLruCache[key] ?: return null
if (bitmap.isRecycled) {
bitmapLruCache.remove(key)
return null
}
return bitmap
} }
fun remove(key: String): Bitmap? { fun remove(key: String): Bitmap? {
@@ -4,7 +4,6 @@ import android.annotation.SuppressLint
import android.content.Context import android.content.Context
import android.graphics.Canvas import android.graphics.Canvas
import android.graphics.RectF import android.graphics.RectF
import android.graphics.drawable.BitmapDrawable
import android.os.Build import android.os.Build
import android.util.AttributeSet import android.util.AttributeSet
import android.view.MotionEvent import android.view.MotionEvent
@@ -617,13 +616,10 @@ class ReadView(context: Context, attrs: AttributeSet) :
* 更新背景 * 更新背景
*/ */
fun upBg() { fun upBg() {
val oldBg = ReadBookConfig.upBg(width, height) ReadBookConfig.upBg(width, height)
curPage.upBg() curPage.upBg()
prevPage.upBg() prevPage.upBg()
nextPage.upBg() nextPage.upBg()
// 所有视图背景更新完成后再 recycle 旧 bitmap
// 防止视图仍持有旧 BitmapDrawable 引用时 bitmap 已被 recycle 导致崩溃
(oldBg as? BitmapDrawable)?.bitmap?.recycle()
} }
/** /**
@@ -25,6 +25,7 @@ class FadePageDelegate(readView: ReadView) : PageDelegate(readView) {
} }
private fun setBitmap() { private fun setBitmap() {
if (!readView.isAttachedToWindow) return
when (mDirection) { when (mDirection) {
PageDirection.PREV -> { PageDirection.PREV -> {
prevPage.screenshot(prevRecorder) prevPage.screenshot(prevRecorder)
@@ -60,7 +61,6 @@ class FadePageDelegate(readView: ReadView) : PageDelegate(readView) {
if (!isMoved || mDirection == PageDirection.NONE) return if (!isMoved || mDirection == PageDirection.NONE) return
// 超过阈值自动翻页,否则回弹 // 超过阈值自动翻页,否则回弹
val shouldFlip = fadeProgress >= flipThreshold val shouldFlip = fadeProgress >= flipThreshold
if (shouldFlip) 1f else 0f
isCancel = !shouldFlip isCancel = !shouldFlip
onAnimStart(readView.defaultAnimationSpeed) onAnimStart(readView.defaultAnimationSpeed)
} }
@@ -97,13 +97,15 @@ class FadePageDelegate(readView: ReadView) : PageDelegate(readView) {
} }
override fun onDraw(canvas: Canvas) { override fun onDraw(canvas: Canvas) {
curRecorder.draw(canvas) if (!readView.isAttachedToWindow) return
if (mDirection == PageDirection.NONE) { if (mDirection == PageDirection.NONE) {
// 直接绘制 curPage,不使用 curRecorder
curPage.draw(canvas) curPage.draw(canvas)
return return
} }
curRecorder.draw(canvas)
val alpha = (fadeProgress * 255).toInt().coerceIn(0, 255) val alpha = (fadeProgress * 255).toInt().coerceIn(0, 255)
val paint = Paint().apply { this.alpha = alpha } val paint = Paint().apply { this.alpha = alpha }
@@ -143,11 +145,12 @@ class FadePageDelegate(readView: ReadView) : PageDelegate(readView) {
readView.fillPage(mDirection) readView.fillPage(mDirection)
// 延迟更新 curRecorder,确保 curPage 已经刷新 // 延迟更新 curRecorder,确保 curPage 已经刷新
readView.post { readView.post {
try { if (readView.isAttachedToWindow) {
curPage.screenshot(curRecorder) try {
readView.invalidate() curPage.screenshot(curRecorder)
} catch (_: Exception) { readView.invalidate()
// 捕获可能的异常,避免闪退 } catch (_: Exception) {
}
} }
} }
} }
@@ -168,12 +171,17 @@ class FadePageDelegate(readView: ReadView) : PageDelegate(readView) {
isMoved = false isMoved = false
isRunning = false isRunning = false
if (!scroller.isFinished) { if (!scroller.isFinished) {
readView.isAbortAnim = true
scroller.abortAnimation() scroller.abortAnimation()
if (!isCancel) { if (!isCancel) {
readView.fillPage(mDirection) readView.fillPage(mDirection)
curPage.screenshot(curRecorder) // 同步 curRecorder if (readView.isAttachedToWindow) {
curPage.screenshot(curRecorder)
}
readView.invalidate() readView.invalidate()
} }
} else {
readView.isAbortAnim = false
} }
} }
@@ -51,15 +51,17 @@ class CanvasRecorderImpl : BaseCanvasRecorder() {
} }
override fun draw(canvas: Canvas) { override fun draw(canvas: Canvas) {
if (bitmap == null) return val b = bitmap ?: return
canvas.drawBitmap(bitmap!!, 0f, 0f, null) if (!b.isRecycled) {
canvas.drawBitmap(b, 0f, 0f, null)
}
} }
override fun recycle() { override fun recycle() {
super.recycle() super.recycle()
val bitmap = bitmap ?: return val b = bitmap ?: return
bitmapPool.put(bitmap) bitmap = null
this.bitmap = null bitmapPool.put(b)
} }
companion object { companion object {