From b8003e91755352dc566b8da1fb7185c7d7389670 Mon Sep 17 00:00:00 2001 From: HapeLee <1321903405@qq.com> Date: Tue, 10 Mar 2026 00:01:30 +0800 Subject: [PATCH] =?UTF-8?q?[=E4=BF=AE=E5=A4=8D]=20Bitmap=20=E6=9C=AA?= =?UTF-8?q?=E6=AD=A3=E7=A1=AE=E5=9B=9E=E6=94=B6=E5=AF=BC=E8=87=B4=E7=9A=84?= =?UTF-8?q?=E9=97=AA=E9=80=80=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/io/legado/app/model/ImageProvider.kt | 16 +++++++----- .../legado/app/ui/book/read/page/ReadView.kt | 6 +---- .../read/page/delegate/FadePageDelegate.kt | 26 ++++++++++++------- .../canvasrecorder/CanvasRecorderImpl.kt | 12 +++++---- 4 files changed, 34 insertions(+), 26 deletions(-) diff --git a/app/src/main/java/io/legado/app/model/ImageProvider.kt b/app/src/main/java/io/legado/app/model/ImageProvider.kt index 94a0ee724..9190334f4 100644 --- a/app/src/main/java/io/legado/app/model/ImageProvider.kt +++ b/app/src/main/java/io/legado/app/model/ImageProvider.kt @@ -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? { diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/ReadView.kt b/app/src/main/java/io/legado/app/ui/book/read/page/ReadView.kt index 8ce103f2d..07b2ae669 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/ReadView.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/ReadView.kt @@ -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() } /** diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/delegate/FadePageDelegate.kt b/app/src/main/java/io/legado/app/ui/book/read/page/delegate/FadePageDelegate.kt index 0d3f622da..16d57c5d2 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/delegate/FadePageDelegate.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/delegate/FadePageDelegate.kt @@ -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 } } diff --git a/app/src/main/java/io/legado/app/utils/canvasrecorder/CanvasRecorderImpl.kt b/app/src/main/java/io/legado/app/utils/canvasrecorder/CanvasRecorderImpl.kt index 08bcd9ecd..4148149d0 100644 --- a/app/src/main/java/io/legado/app/utils/canvasrecorder/CanvasRecorderImpl.kt +++ b/app/src/main/java/io/legado/app/utils/canvasrecorder/CanvasRecorderImpl.kt @@ -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 {