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