diff --git a/app/src/main/java/io/legado/app/data/entities/BaseSource.kt b/app/src/main/java/io/legado/app/data/entities/BaseSource.kt index 0a4461d63..99ab1b9c5 100644 --- a/app/src/main/java/io/legado/app/data/entities/BaseSource.kt +++ b/app/src/main/java/io/legado/app/data/entities/BaseSource.kt @@ -8,6 +8,7 @@ import io.legado.app.constant.AppLog import io.legado.app.data.entities.rule.RowUi import io.legado.app.help.CacheManager import io.legado.app.help.JsExtensions +import io.legado.app.help.SymmetricCryptoAndroid import io.legado.app.help.config.AppConfig import io.legado.app.help.http.CookieStore import io.legado.app.model.SharedJsScope @@ -106,6 +107,7 @@ interface BaseSource : JsExtensions { it.lastIndexOf("<") ) ).toString() + else -> it } ).getOrNull()?.let { map -> @@ -176,7 +178,7 @@ interface BaseSource : JsExtensions { fun putLoginInfo(info: String): Boolean { return try { val key = (AppConst.androidId).encodeToByteArray(0, 16) - val encodeStr = AES(key).encryptBase64(info) + val encodeStr = SymmetricCryptoAndroid("AES", key).encryptBase64(info) CacheManager.put("userInfo_${getKey()}", encodeStr) true } catch (e: Exception) { diff --git a/app/src/main/java/io/legado/app/help/JsEncodeUtils.kt b/app/src/main/java/io/legado/app/help/JsEncodeUtils.kt index a98b6441f..3f3e0e3f8 100644 --- a/app/src/main/java/io/legado/app/help/JsEncodeUtils.kt +++ b/app/src/main/java/io/legado/app/help/JsEncodeUtils.kt @@ -42,7 +42,7 @@ interface JsEncodeUtils { key: ByteArray?, iv: ByteArray? ): SymmetricCrypto { - val symmetricCrypto = SymmetricCrypto(transformation, key) + val symmetricCrypto = SymmetricCryptoAndroid(transformation, key) return if (iv != null && iv.isNotEmpty()) symmetricCrypto.setIv(iv) else symmetricCrypto } diff --git a/app/src/main/java/io/legado/app/help/PaintPool.kt b/app/src/main/java/io/legado/app/help/PaintPool.kt new file mode 100644 index 000000000..953b5375b --- /dev/null +++ b/app/src/main/java/io/legado/app/help/PaintPool.kt @@ -0,0 +1,17 @@ +package io.legado.app.help + +import android.graphics.Paint +import io.legado.app.utils.objectpool.BaseSafeObjectPool + +object PaintPool : BaseSafeObjectPool(8) { + + private val emptyPaint = Paint() + + override fun create(): Paint = Paint() + + override fun recycle(target: Paint) { + target.set(emptyPaint) + super.recycle(target) + } + +} diff --git a/app/src/main/java/io/legado/app/help/SymmetricCryptoAndroid.kt b/app/src/main/java/io/legado/app/help/SymmetricCryptoAndroid.kt new file mode 100644 index 000000000..201219930 --- /dev/null +++ b/app/src/main/java/io/legado/app/help/SymmetricCryptoAndroid.kt @@ -0,0 +1,33 @@ +package io.legado.app.help + +import cn.hutool.crypto.symmetric.SymmetricCrypto +import io.legado.app.utils.EncoderUtils +import java.io.InputStream +import java.nio.charset.Charset + +class SymmetricCryptoAndroid( + algorithm: String, + key: ByteArray?, +) : SymmetricCrypto(algorithm, key) { + + override fun encryptBase64(data: ByteArray): String { + return EncoderUtils.base64Encode(encrypt(data)) + } + + override fun encryptBase64(data: String, charset: String?): String { + return EncoderUtils.base64Encode(encrypt(data, charset)) + } + + override fun encryptBase64(data: String, charset: Charset?): String { + return EncoderUtils.base64Encode(encrypt(data, charset)) + } + + override fun encryptBase64(data: String): String { + return EncoderUtils.base64Encode(encrypt(data)) + } + + override fun encryptBase64(data: InputStream): String { + return EncoderUtils.base64Encode(encrypt(data)) + } + +} diff --git a/app/src/main/java/io/legado/app/help/config/ThemeConfig.kt b/app/src/main/java/io/legado/app/help/config/ThemeConfig.kt index 53dd5ee22..2296d30a6 100644 --- a/app/src/main/java/io/legado/app/help/config/ThemeConfig.kt +++ b/app/src/main/java/io/legado/app/help/config/ThemeConfig.kt @@ -55,7 +55,6 @@ object ThemeConfig { initNightMode() BookCover.upDefaultCover() postEvent(EventBus.RECREATE, "") - postEvent(EventBus.UP_CONFIG, arrayOf(2, 9)) } private fun initNightMode() { @@ -74,10 +73,12 @@ object ThemeConfig { context.getPrefString(PreferKey.bgImage), context.getPrefInt(PreferKey.bgImageBlurring, 0) ) + Theme.Dark -> Pair( context.getPrefString(PreferKey.bgImageN), context.getPrefInt(PreferKey.bgImageNBlurring, 0) ) + else -> null } ?: return null if (bgCfg.first.isNullOrBlank()) return null @@ -218,6 +219,7 @@ object ThemeConfig { .bottomBackground(Color.WHITE) .apply() } + AppConfig.isNightTheme -> { val primary = getPrefInt(PreferKey.cNPrimary, getCompatColor(R.color.md_blue_grey_600)) @@ -238,6 +240,7 @@ object ThemeConfig { .bottomBackground(ColorUtils.withAlpha(bBackground, 1f)) .apply() } + else -> { val primary = getPrefInt(PreferKey.cPrimary, getCompatColor(R.color.md_brown_500)) diff --git a/app/src/main/java/io/legado/app/service/DownloadService.kt b/app/src/main/java/io/legado/app/service/DownloadService.kt index 4db6bd324..3e119c026 100644 --- a/app/src/main/java/io/legado/app/service/DownloadService.kt +++ b/app/src/main/java/io/legado/app/service/DownloadService.kt @@ -250,12 +250,12 @@ class DownloadService : BaseService() { .setSubText(getString(R.string.action_download)) .setContentTitle(content) .setContentIntent( - servicePendingIntent(IntentAction.play) { + servicePendingIntent(IntentAction.play, downloadId.toInt()) { putExtra("downloadId", downloadId) } ) .setDeleteIntent( - servicePendingIntent(IntentAction.stop) { + servicePendingIntent(IntentAction.stop, downloadId.toInt()) { putExtra("downloadId", downloadId) } ) diff --git a/app/src/main/java/io/legado/app/ui/book/read/ReadBookViewModel.kt b/app/src/main/java/io/legado/app/ui/book/read/ReadBookViewModel.kt index d6d78c7cd..4426655e6 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/ReadBookViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/ReadBookViewModel.kt @@ -422,7 +422,7 @@ class ReadBookViewModel(application: Application) : BaseViewModel(application) { var curLine = curTextLines[lineIndex] length = length - currentPage.text.length + curLine.text.length if (curLine.isParagraphEnd) length++ - while (length < contentPosition && lineIndex + 1 < curTextLines.size) { + while (length <= contentPosition && lineIndex + 1 < curTextLines.size) { lineIndex += 1 curLine = curTextLines[lineIndex] length += curLine.text.length 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 6173b9265..fff5a04f9 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 @@ -31,6 +31,7 @@ import io.legado.app.ui.book.read.page.provider.ChapterProvider import io.legado.app.ui.book.read.page.provider.LayoutProgressListener import io.legado.app.ui.book.read.page.provider.TextPageFactory import io.legado.app.utils.activity +import io.legado.app.utils.canvasrecorder.pools.BitmapPool import io.legado.app.utils.invisible import io.legado.app.utils.showDialogFragment import io.legado.app.utils.throttle @@ -460,6 +461,8 @@ class ReadView(context: Context, attrs: AttributeSet) : fun onDestroy() { pageDelegate?.onDestroy() curPage.cancelSelect() + invalidateTextPage() + BitmapPool.clear() } /** diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextLine.kt b/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextLine.kt index 2adc81ea6..963639e73 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextLine.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextLine.kt @@ -1,14 +1,20 @@ package io.legado.app.ui.book.read.page.entities +import android.annotation.SuppressLint import android.graphics.Canvas import android.graphics.Paint.FontMetrics +import android.os.Build import androidx.annotation.Keep +import androidx.core.graphics.withTranslation +import io.legado.app.help.PaintPool import io.legado.app.help.book.isImage import io.legado.app.help.config.ReadBookConfig +import io.legado.app.lib.theme.ThemeStore import io.legado.app.model.ReadBook import io.legado.app.ui.book.read.page.ContentTextView import io.legado.app.ui.book.read.page.entities.TextPage.Companion.emptyTextPage import io.legado.app.ui.book.read.page.entities.column.BaseColumn +import io.legado.app.ui.book.read.page.entities.column.TextColumn import io.legado.app.ui.book.read.page.provider.ChapterProvider import io.legado.app.utils.canvasrecorder.CanvasRecorderFactory import io.legado.app.utils.canvasrecorder.recordIfNeededThenDraw @@ -32,6 +38,13 @@ data class TextLine( val isTitle: Boolean = false, var isParagraphEnd: Boolean = false, var isImage: Boolean = false, + var startX: Float = 0f, + var indentSize: Int = 0, + var extraLetterSpacing: Float = 0f, + var extraLetterSpacingOffsetX: Float = 0f, + var wordSpacing: Float = 0f, + var exceed: Boolean = false, + var onlyTextColumn: Boolean = true, ) { val columns: List get() = textColumns @@ -41,6 +54,7 @@ data class TextLine( val chapterIndices: IntRange get() = chapterPosition..chapterPosition + charSize val height: Float inline get() = lineBottom - lineTop val canvasRecorder = CanvasRecorderFactory.create() + var searchResultColumnCount = 0 var isReadAloud: Boolean = false set(value) { if (field != value) { @@ -52,6 +66,9 @@ data class TextLine( var isLeftLine = true fun addColumn(column: BaseColumn) { + if (column !is TextColumn) { + onlyTextColumn = false + } column.textLine = this textColumns.add(column) } @@ -129,14 +146,57 @@ data class TextLine( } private fun drawTextLine(view: ContentTextView, canvas: Canvas) { - for (i in columns.indices) { - columns[i].draw(view, canvas) + if (checkFastDraw()) { + fastDrawTextLine(view, canvas) + } else { + for (i in columns.indices) { + columns[i].draw(view, canvas) + } } if (ReadBookConfig.underline && !isImage && ReadBook.book?.isImage != true) { drawUnderline(canvas) } } + @SuppressLint("NewApi") + private fun fastDrawTextLine(view: ContentTextView, canvas: Canvas) { + val textPaint = if (isTitle) { + ChapterProvider.titlePaint + } else { + ChapterProvider.contentPaint + } + val textColor = if (isReadAloud) { + ThemeStore.accentColor + } else { + ReadBookConfig.textColor + } + val paint = PaintPool.obtain() + paint.set(textPaint) + if (extraLetterSpacing != 0f) { + paint.letterSpacing += extraLetterSpacing + } + if (wordSpacing != 0f) { + paint.wordSpacing = wordSpacing + } + if (paint.color != textColor) { + paint.color = textColor + } + if (extraLetterSpacingOffsetX != 0f) { + canvas.withTranslation(extraLetterSpacingOffsetX) { + canvas.drawText(text, indentSize, text.length, startX, lineBase - lineTop, paint) + } + } else { + canvas.drawText(text, indentSize, text.length, startX, lineBase - lineTop, paint) + } + PaintPool.recycle(paint) + for (i in columns.indices) { + val column = columns[i] as TextColumn + if (column.selected) { + canvas.drawRect(column.start, 0f, column.end, height, view.selectedPaint) + } + } + } + /** * 绘制下划线 */ @@ -151,6 +211,16 @@ data class TextLine( ) } + fun checkFastDraw(): Boolean { + if (exceed || !onlyTextColumn || textPage.isMsgPage) { + return false + } + if (!atLeastApi29 && wordSpacing != 0f) { + return false + } + return searchResultColumnCount == 0 + } + fun invalidate() { invalidateSelf() textPage.invalidate() @@ -166,6 +236,7 @@ data class TextLine( companion object { val emptyTextLine = TextLine() + private val atLeastApi29 = Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q } } diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextPage.kt b/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextPage.kt index a787933a2..07877065f 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextPage.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/entities/TextPage.kt @@ -1,11 +1,13 @@ package io.legado.app.ui.book.read.page.entities import android.graphics.Canvas +import android.graphics.Paint import android.text.Layout import android.text.StaticLayout import androidx.annotation.Keep import androidx.core.graphics.withTranslation import io.legado.app.R +import io.legado.app.help.PaintPool import io.legado.app.help.config.ReadBookConfig import io.legado.app.ui.book.read.page.ContentTextView import io.legado.app.ui.book.read.page.entities.TextChapter.Companion.emptyTextChapter @@ -13,6 +15,7 @@ import io.legado.app.ui.book.read.page.entities.column.TextColumn import io.legado.app.ui.book.read.page.provider.ChapterProvider import io.legado.app.utils.canvasrecorder.CanvasRecorderFactory import io.legado.app.utils.canvasrecorder.recordIfNeeded +import io.legado.app.utils.dpToPx import splitties.init.appCtx import java.text.DecimalFormat import kotlin.math.min @@ -45,7 +48,7 @@ data class TextPage( var isMsgPage: Boolean = false var canvasRecorder = CanvasRecorderFactory.create(true) var doublePage = false - var paddingTop = 0 + var paddingTop = ChapterProvider.paddingTop var isCompleted = false @JvmField @@ -277,6 +280,21 @@ data class TextPage( } } + private fun drawDebugInfo(canvas: Canvas) { + ChapterProvider.run { + val paint = PaintPool.obtain() + paint.style = Paint.Style.STROKE + canvas.drawRect( + paddingLeft.toFloat(), + 0f, + (paddingLeft + visibleWidth).toFloat(), + height - 1.dpToPx(), + paint + ) + PaintPool.recycle(paint) + } + } + private fun drawPage(view: ContentTextView, canvas: Canvas) { for (i in lines.indices) { val line = lines[i] diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/entities/column/TextColumn.kt b/app/src/main/java/io/legado/app/ui/book/read/page/entities/column/TextColumn.kt index de204e42d..cf010a08c 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/entities/column/TextColumn.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/entities/column/TextColumn.kt @@ -2,6 +2,7 @@ package io.legado.app.ui.book.read.page.entities.column import android.graphics.Canvas import androidx.annotation.Keep +import io.legado.app.help.PaintPool import io.legado.app.help.config.ReadBookConfig import io.legado.app.lib.theme.ThemeStore import io.legado.app.ui.book.read.page.ContentTextView @@ -32,6 +33,11 @@ data class TextColumn( set(value) { if (field != value) { textLine.invalidate() + if (value) { + textLine.searchResultColumnCount++ + } else { + textLine.searchResultColumnCount-- + } } field = value } @@ -42,15 +48,18 @@ data class TextColumn( } else { ChapterProvider.contentPaint } - if (textLine.isReadAloud || isSearchResult) { - synchronized(textPaint) { - textPaint.color = ThemeStore.accentColor - canvas.drawText(charData, start, textLine.lineBase - textLine.lineTop, textPaint) - textPaint.color = ReadBookConfig.textColor - } + val textColor = if (textLine.isReadAloud || isSearchResult) { + ThemeStore.accentColor } else { - canvas.drawText(charData, start, textLine.lineBase - textLine.lineTop, textPaint) + ReadBookConfig.textColor } + val paint = PaintPool.obtain() + paint.set(textPaint) + if (paint.color != textColor) { + paint.color = textColor + } + canvas.drawText(charData, start, textLine.lineBase - textLine.lineTop, paint) + PaintPool.recycle(paint) if (selected) { canvas.drawRect(start, 0f, end, textLine.height, view.selectedPaint) } diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/provider/ChapterProvider.kt b/app/src/main/java/io/legado/app/ui/book/read/page/provider/ChapterProvider.kt index 8e4fa240d..bd36adc73 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/provider/ChapterProvider.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/provider/ChapterProvider.kt @@ -879,6 +879,7 @@ object ChapterProvider { tPaint.typeface = titleFont tPaint.textSize = with(ReadBookConfig) { textSize + titleSize }.toFloat().spToPx() tPaint.isAntiAlias = true + tPaint.isLinearText = true //正文 val cPaint = TextPaint() cPaint.color = ReadBookConfig.textColor @@ -886,6 +887,7 @@ object ChapterProvider { cPaint.typeface = textFont cPaint.textSize = ReadBookConfig.textSize.toFloat().spToPx() cPaint.isAntiAlias = true + cPaint.isLinearText = true return Pair(tPaint, cPaint) } diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/provider/TextChapterLayout.kt b/app/src/main/java/io/legado/app/ui/book/read/page/provider/TextChapterLayout.kt index af21d4721..4d097077e 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/provider/TextChapterLayout.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/provider/TextChapterLayout.kt @@ -31,6 +31,7 @@ import kotlinx.coroutines.launch import java.util.LinkedList import java.util.Locale import kotlin.coroutines.coroutineContext +import kotlin.math.roundToInt class TextChapterLayout( scope: CoroutineScope, @@ -467,7 +468,7 @@ class TextChapterLayout( //第一行 非标题 textLine.text = lineText addCharsToLineFirst( - book, absStartX, textLine, words, + book, absStartX, textLine, words, textPaint, desiredWidth, widths, srcList ) } @@ -505,7 +506,7 @@ class TextChapterLayout( //中间行 textLine.text = lineText addCharsToLineMiddle( - book, absStartX, textLine, words, + book, absStartX, textLine, words, textPaint, desiredWidth, 0f, widths, srcList ) } @@ -556,6 +557,7 @@ class TextChapterLayout( absStartX: Int, textLine: TextLine, words: List, + textPaint: TextPaint, /**自然排版长度**/ desiredWidth: Float, textWidths: List, @@ -582,11 +584,12 @@ class TextChapterLayout( x = x1 textLine.indentWidth = x } + textLine.indentSize = bodyIndent.length if (words.size > bodyIndent.length) { val text1 = words.subList(bodyIndent.length, words.size) val textWidths1 = textWidths.subList(bodyIndent.length, textWidths.size) addCharsToLineMiddle( - book, absStartX, textLine, text1, + book, absStartX, textLine, text1, textPaint, desiredWidth, x, textWidths1, srcList ) } @@ -600,6 +603,7 @@ class TextChapterLayout( absStartX: Int, textLine: TextLine, words: List, + textPaint: TextPaint, /**自然排版长度**/ desiredWidth: Float, /**起始x坐标**/ @@ -616,8 +620,10 @@ class TextChapterLayout( } val residualWidth = visibleWidth - desiredWidth val spaceSize = words.count { it == " " } + textLine.startX = absStartX + startX if (spaceSize > 1) { - val d = residualWidth / spaceSize + val d = residualWidth / (spaceSize - 1) + textLine.wordSpacing = d var x = startX for (index in words.indices) { val char = words[index] @@ -636,6 +642,8 @@ class TextChapterLayout( } else { val gapCount: Int = words.lastIndex val d = residualWidth / gapCount + textLine.extraLetterSpacingOffsetX = -d / 2 + textLine.extraLetterSpacing = d / textPaint.textSize var x = startX for (index in words.indices) { val char = words[index] @@ -666,6 +674,7 @@ class TextChapterLayout( ) { val indentLength = ReadBookConfig.paragraphIndent.length var x = startX + textLine.startX = absStartX + startX for (index in words.indices) { val char = words[index] val cw = textWidths[index] @@ -727,8 +736,9 @@ class TextChapterLayout( */ private fun exceed(absStartX: Int, textLine: TextLine, words: List) { val visibleEnd = absStartX + visibleWidth - val endX = textLine.columns.lastOrNull()?.end ?: return + val endX = textLine.columns.lastOrNull()?.end?.roundToInt() ?: return if (endX > visibleEnd) { + textLine.exceed = true val cc = (endX - visibleEnd) / words.size for (i in 0..words.lastIndex) { textLine.getColumnReverseAt(i).let { diff --git a/app/src/main/java/io/legado/app/utils/ContextExtensions.kt b/app/src/main/java/io/legado/app/utils/ContextExtensions.kt index 09c940ce2..e9bb62cfb 100644 --- a/app/src/main/java/io/legado/app/utils/ContextExtensions.kt +++ b/app/src/main/java/io/legado/app/utils/ContextExtensions.kt @@ -63,6 +63,7 @@ inline fun Context.stopService() { @SuppressLint("UnspecifiedImmutableFlag") inline fun Context.servicePendingIntent( action: String, + requestCode: Int = 0, configIntent: Intent.() -> Unit = {} ): PendingIntent? { val intent = Intent(this, T::class.java) @@ -73,7 +74,7 @@ inline fun Context.servicePendingIntent( } else { FLAG_UPDATE_CURRENT } - return getService(this, 0, intent, flags) + return getService(this, requestCode, intent, flags) } @SuppressLint("UnspecifiedImmutableFlag") diff --git a/app/src/main/java/io/legado/app/utils/EncoderUtils.kt b/app/src/main/java/io/legado/app/utils/EncoderUtils.kt index 54c528f03..97f4d1dca 100644 --- a/app/src/main/java/io/legado/app/utils/EncoderUtils.kt +++ b/app/src/main/java/io/legado/app/utils/EncoderUtils.kt @@ -37,6 +37,11 @@ object EncoderUtils { fun base64Encode(str: String, flags: Int = Base64.NO_WRAP): String? { return Base64.encodeToString(str.toByteArray(), flags) } + + @JvmOverloads + fun base64Encode(bytes: ByteArray, flags: Int = Base64.NO_WRAP): String { + return Base64.encodeToString(bytes, flags) + } @JvmOverloads fun base64DecodeToByteArray(str: String, flags: Int = Base64.DEFAULT): ByteArray { diff --git a/app/src/main/java/io/legado/app/utils/canvasrecorder/CanvasRecorderApi23Impl.kt b/app/src/main/java/io/legado/app/utils/canvasrecorder/CanvasRecorderApi23Impl.kt index f9c19cac6..bc0964f53 100644 --- a/app/src/main/java/io/legado/app/utils/canvasrecorder/CanvasRecorderApi23Impl.kt +++ b/app/src/main/java/io/legado/app/utils/canvasrecorder/CanvasRecorderApi23Impl.kt @@ -2,8 +2,8 @@ package io.legado.app.utils.canvasrecorder import android.graphics.Canvas import android.graphics.Picture -import io.legado.app.utils.canvasrecorder.objectpool.synchronized import io.legado.app.utils.canvasrecorder.pools.PicturePool +import io.legado.app.utils.objectpool.synchronized class CanvasRecorderApi23Impl : BaseCanvasRecorder() { diff --git a/app/src/main/java/io/legado/app/utils/canvasrecorder/CanvasRecorderApi29Impl.kt b/app/src/main/java/io/legado/app/utils/canvasrecorder/CanvasRecorderApi29Impl.kt index 7fd6dbd7d..ce7205c88 100644 --- a/app/src/main/java/io/legado/app/utils/canvasrecorder/CanvasRecorderApi29Impl.kt +++ b/app/src/main/java/io/legado/app/utils/canvasrecorder/CanvasRecorderApi29Impl.kt @@ -5,7 +5,7 @@ import android.graphics.Picture import android.graphics.RenderNode import android.os.Build import androidx.annotation.RequiresApi -import io.legado.app.utils.canvasrecorder.objectpool.synchronized +import io.legado.app.utils.objectpool.synchronized import io.legado.app.utils.canvasrecorder.pools.PicturePool import io.legado.app.utils.canvasrecorder.pools.RenderNodePool 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 3cd205292..59e3c5cb0 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 @@ -16,14 +16,14 @@ class CanvasRecorderImpl : BaseCanvasRecorder() { private fun init(width: Int, height: Int) { if (bitmap == null) { - bitmap = bitmapPool.obtain(width, height) + bitmap = BitmapPool.obtain(width, height) } if (bitmap!!.width != width || bitmap!!.height != height) { if (canReconfigure(width, height)) { bitmap!!.reconfigure(width, height, Bitmap.Config.ARGB_8888) } else { - bitmapPool.recycle(bitmap!!) - bitmap = bitmapPool.obtain(width, height) + BitmapPool.recycle(bitmap!!) + bitmap = BitmapPool.obtain(width, height) } } } @@ -54,13 +54,12 @@ class CanvasRecorderImpl : BaseCanvasRecorder() { override fun recycle() { super.recycle() val bitmap = bitmap ?: return - bitmapPool.recycle(bitmap) + BitmapPool.recycle(bitmap) this.bitmap = null } companion object { private val canvasPool = CanvasPool(2) - private val bitmapPool = BitmapPool() } } diff --git a/app/src/main/java/io/legado/app/utils/canvasrecorder/objectpool/BaseObjectPool.kt b/app/src/main/java/io/legado/app/utils/canvasrecorder/objectpool/BaseObjectPool.kt deleted file mode 100644 index 59b7410de..000000000 --- a/app/src/main/java/io/legado/app/utils/canvasrecorder/objectpool/BaseObjectPool.kt +++ /dev/null @@ -1,24 +0,0 @@ -package io.legado.app.utils.canvasrecorder.objectpool - -import androidx.annotation.CallSuper -import java.lang.ref.SoftReference -import java.util.LinkedList - -abstract class BaseObjectPool : ObjectPool { - - private val pool = LinkedList>() - - override fun obtain(): T { - while (true) { - if (pool.isEmpty()) break - return pool.poll()?.get() ?: continue - } - return create() - } - - @CallSuper - override fun recycle(target: T) { - pool.add(SoftReference(target)) - } - -} diff --git a/app/src/main/java/io/legado/app/utils/canvasrecorder/pools/BitmapPool.kt b/app/src/main/java/io/legado/app/utils/canvasrecorder/pools/BitmapPool.kt index 62c5845e3..a9539035e 100644 --- a/app/src/main/java/io/legado/app/utils/canvasrecorder/pools/BitmapPool.kt +++ b/app/src/main/java/io/legado/app/utils/canvasrecorder/pools/BitmapPool.kt @@ -1,15 +1,46 @@ package io.legado.app.utils.canvasrecorder.pools import android.graphics.Bitmap -import java.lang.ref.SoftReference +import io.legado.app.help.globalExecutor import java.util.concurrent.ConcurrentHashMap -class BitmapPool { +object BitmapPool { - private val reusableBitmaps: MutableSet> = ConcurrentHashMap.newKeySet() + private val reusableBitmaps: MutableSet = ConcurrentHashMap.newKeySet() fun recycle(bitmap: Bitmap) { - reusableBitmaps.add(SoftReference(bitmap)) + reusableBitmaps.add(bitmap) + trimSize() + } + + fun clear() { + if (reusableBitmaps.isEmpty()) { + return + } + globalExecutor.execute { + val iterator = reusableBitmaps.iterator() + while (iterator.hasNext()) { + val item = iterator.next() + iterator.remove() + item.recycle() + } + } + } + + private fun trimSize() { + globalExecutor.execute { + var byteCount = 0 + val iterator = reusableBitmaps.iterator() + while (iterator.hasNext()) { + val item = iterator.next() + if (byteCount > 64 * 1024 * 1024) { + iterator.remove() + item.recycle() + } else { + byteCount += item.byteCount + } + } + } } fun obtain(width: Int, height: Int): Bitmap { @@ -18,7 +49,7 @@ class BitmapPool { } val iterator = reusableBitmaps.iterator() while (iterator.hasNext()) { - val item = iterator.next().get() ?: continue + val item = iterator.next() if (item.isMutable) { // Check to see it the item can be used for inBitmap. if (canReconfigure(item, width, height)) { diff --git a/app/src/main/java/io/legado/app/utils/canvasrecorder/pools/PicturePool.kt b/app/src/main/java/io/legado/app/utils/canvasrecorder/pools/PicturePool.kt index ca0f25572..cdf35774c 100644 --- a/app/src/main/java/io/legado/app/utils/canvasrecorder/pools/PicturePool.kt +++ b/app/src/main/java/io/legado/app/utils/canvasrecorder/pools/PicturePool.kt @@ -1,9 +1,9 @@ package io.legado.app.utils.canvasrecorder.pools import android.graphics.Picture -import io.legado.app.utils.canvasrecorder.objectpool.BaseObjectPool +import io.legado.app.utils.objectpool.BaseObjectPool -class PicturePool : BaseObjectPool() { +class PicturePool : BaseObjectPool(64) { override fun create(): Picture = Picture() diff --git a/app/src/main/java/io/legado/app/utils/canvasrecorder/pools/RenderNodePool.kt b/app/src/main/java/io/legado/app/utils/canvasrecorder/pools/RenderNodePool.kt index 3cf1fda2b..b7a9e8ca0 100644 --- a/app/src/main/java/io/legado/app/utils/canvasrecorder/pools/RenderNodePool.kt +++ b/app/src/main/java/io/legado/app/utils/canvasrecorder/pools/RenderNodePool.kt @@ -3,10 +3,10 @@ package io.legado.app.utils.canvasrecorder.pools import android.graphics.RenderNode import android.os.Build import androidx.annotation.RequiresApi -import io.legado.app.utils.canvasrecorder.objectpool.BaseObjectPool +import io.legado.app.utils.objectpool.BaseObjectPool @RequiresApi(Build.VERSION_CODES.Q) -class RenderNodePool : BaseObjectPool() { +class RenderNodePool : BaseObjectPool(64) { override fun recycle(target: RenderNode) { target.discardDisplayList() diff --git a/app/src/main/java/io/legado/app/utils/objectpool/BaseObjectPool.kt b/app/src/main/java/io/legado/app/utils/objectpool/BaseObjectPool.kt new file mode 100644 index 000000000..b40201f70 --- /dev/null +++ b/app/src/main/java/io/legado/app/utils/objectpool/BaseObjectPool.kt @@ -0,0 +1,19 @@ +package io.legado.app.utils.objectpool + +import androidx.annotation.CallSuper +import androidx.core.util.Pools + +abstract class BaseObjectPool(size: Int) : ObjectPool { + + open val pool = Pools.SimplePool(size) + + override fun obtain(): T { + return pool.acquire() ?: create() + } + + @CallSuper + override fun recycle(target: T) { + pool.release(target) + } + +} diff --git a/app/src/main/java/io/legado/app/utils/objectpool/BaseSafeObjectPool.kt b/app/src/main/java/io/legado/app/utils/objectpool/BaseSafeObjectPool.kt new file mode 100644 index 000000000..d5fb1eca6 --- /dev/null +++ b/app/src/main/java/io/legado/app/utils/objectpool/BaseSafeObjectPool.kt @@ -0,0 +1,9 @@ +package io.legado.app.utils.objectpool + +import androidx.core.util.Pools + +abstract class BaseSafeObjectPool(size: Int): BaseObjectPool(size) { + + override val pool = Pools.SynchronizedPool(size) + +} diff --git a/app/src/main/java/io/legado/app/utils/canvasrecorder/objectpool/ObjectPool.kt b/app/src/main/java/io/legado/app/utils/objectpool/ObjectPool.kt similarity index 64% rename from app/src/main/java/io/legado/app/utils/canvasrecorder/objectpool/ObjectPool.kt rename to app/src/main/java/io/legado/app/utils/objectpool/ObjectPool.kt index 71fd824c6..f63a14d79 100644 --- a/app/src/main/java/io/legado/app/utils/canvasrecorder/objectpool/ObjectPool.kt +++ b/app/src/main/java/io/legado/app/utils/objectpool/ObjectPool.kt @@ -1,4 +1,4 @@ -package io.legado.app.utils.canvasrecorder.objectpool +package io.legado.app.utils.objectpool interface ObjectPool { diff --git a/app/src/main/java/io/legado/app/utils/canvasrecorder/objectpool/ObjectPoolExtensions.kt b/app/src/main/java/io/legado/app/utils/objectpool/ObjectPoolExtensions.kt similarity index 59% rename from app/src/main/java/io/legado/app/utils/canvasrecorder/objectpool/ObjectPoolExtensions.kt rename to app/src/main/java/io/legado/app/utils/objectpool/ObjectPoolExtensions.kt index c623ddee6..a38c4d521 100644 --- a/app/src/main/java/io/legado/app/utils/canvasrecorder/objectpool/ObjectPoolExtensions.kt +++ b/app/src/main/java/io/legado/app/utils/objectpool/ObjectPoolExtensions.kt @@ -1,3 +1,3 @@ -package io.legado.app.utils.canvasrecorder.objectpool +package io.legado.app.utils.objectpool fun ObjectPool.synchronized(): ObjectPool = ObjectPoolLocked(this) diff --git a/app/src/main/java/io/legado/app/utils/canvasrecorder/objectpool/ObjectPoolLocked.kt b/app/src/main/java/io/legado/app/utils/objectpool/ObjectPoolLocked.kt similarity index 84% rename from app/src/main/java/io/legado/app/utils/canvasrecorder/objectpool/ObjectPoolLocked.kt rename to app/src/main/java/io/legado/app/utils/objectpool/ObjectPoolLocked.kt index 893ac7731..890067b6e 100644 --- a/app/src/main/java/io/legado/app/utils/canvasrecorder/objectpool/ObjectPoolLocked.kt +++ b/app/src/main/java/io/legado/app/utils/objectpool/ObjectPoolLocked.kt @@ -1,4 +1,4 @@ -package io.legado.app.utils.canvasrecorder.objectpool +package io.legado.app.utils.objectpool class ObjectPoolLocked(private val delegate: ObjectPool) : ObjectPool by delegate {