From a327d6d53afd9acf33c76113a6735f3dc7be390e Mon Sep 17 00:00:00 2001 From: Xwite <1797350009@qq.com> Date: Thu, 14 Apr 2022 11:41:29 +0800 Subject: [PATCH 1/6] refactor:add BaseLocalBookParse interface --- .../app/model/localBook/BaseLocalBookParse.kt | 21 +++++++++++++++++++ .../io/legado/app/model/localBook/EpubFile.kt | 10 ++++----- .../io/legado/app/model/localBook/README.md | 7 +++++++ .../io/legado/app/model/localBook/UmdFile.kt | 10 ++++----- 4 files changed, 38 insertions(+), 10 deletions(-) create mode 100644 app/src/main/java/io/legado/app/model/localBook/BaseLocalBookParse.kt create mode 100644 app/src/main/java/io/legado/app/model/localBook/README.md diff --git a/app/src/main/java/io/legado/app/model/localBook/BaseLocalBookParse.kt b/app/src/main/java/io/legado/app/model/localBook/BaseLocalBookParse.kt new file mode 100644 index 000000000..8d59bfc23 --- /dev/null +++ b/app/src/main/java/io/legado/app/model/localBook/BaseLocalBookParse.kt @@ -0,0 +1,21 @@ +package io.legado.app.model.localBook + +import io.legado.app.data.entities.Book +import io.legado.app.data.entities.BookChapter +import java.io.InputStream + +/** + *companion object interface + *see EpubFile.kt + */ +interface BaseLocalBookParse { + + fun upBookInfo(book: Book) + + fun getChapterList(book: Book): ArrayList + + fun getContent(book: Book, chapter: BookChapter): String? + + fun getImage(book: Book, href: String): InputStream? + +} diff --git a/app/src/main/java/io/legado/app/model/localBook/EpubFile.kt b/app/src/main/java/io/legado/app/model/localBook/EpubFile.kt index 36be77a05..ffdaf89e0 100644 --- a/app/src/main/java/io/legado/app/model/localBook/EpubFile.kt +++ b/app/src/main/java/io/legado/app/model/localBook/EpubFile.kt @@ -25,7 +25,7 @@ import java.util.zip.ZipFile class EpubFile(var book: Book) { - companion object { + companion object : BaseLocalBookParse { private var eFile: EpubFile? = null @Synchronized @@ -41,17 +41,17 @@ class EpubFile(var book: Book) { } @Synchronized - fun getChapterList(book: Book): ArrayList { + override fun getChapterList(book: Book): ArrayList { return getEFile(book).getChapterList() } @Synchronized - fun getContent(book: Book, chapter: BookChapter): String? { + override fun getContent(book: Book, chapter: BookChapter): String? { return getEFile(book).getContent(chapter) } @Synchronized - fun getImage( + override fun getImage( book: Book, href: String ): InputStream? { @@ -59,7 +59,7 @@ class EpubFile(var book: Book) { } @Synchronized - fun upBookInfo(book: Book) { + override fun upBookInfo(book: Book) { return getEFile(book).upBookInfo() } } diff --git a/app/src/main/java/io/legado/app/model/localBook/README.md b/app/src/main/java/io/legado/app/model/localBook/README.md new file mode 100644 index 000000000..8f080b25e --- /dev/null +++ b/app/src/main/java/io/legado/app/model/localBook/README.md @@ -0,0 +1,7 @@ +# 本地书籍解析 + +* BaseLocalBookParse.kt 本地书籍解析接口 +* LocalBook.kt 总入口 +* TextFile.kt 解析txt +* EpubFile.kt 解析epub +* UmdFile.kt 解析umd \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/model/localBook/UmdFile.kt b/app/src/main/java/io/legado/app/model/localBook/UmdFile.kt index 811e65d71..9e1a38b48 100644 --- a/app/src/main/java/io/legado/app/model/localBook/UmdFile.kt +++ b/app/src/main/java/io/legado/app/model/localBook/UmdFile.kt @@ -11,7 +11,7 @@ import java.io.File import java.io.InputStream class UmdFile(var book: Book) { - companion object { + companion object : BaseLocalBookParse { private var uFile: UmdFile? = null @Synchronized @@ -25,17 +25,17 @@ class UmdFile(var book: Book) { } @Synchronized - fun getChapterList(book: Book): ArrayList { + override fun getChapterList(book: Book): ArrayList { return getUFile(book).getChapterList() } @Synchronized - fun getContent(book: Book, chapter: BookChapter): String? { + override fun getContent(book: Book, chapter: BookChapter): String? { return getUFile(book).getContent(chapter) } @Synchronized - fun getImage( + override fun getImage( book: Book, href: String ): InputStream? { @@ -44,7 +44,7 @@ class UmdFile(var book: Book) { @Synchronized - fun upBookInfo(book: Book) { + override fun upBookInfo(book: Book) { return getUFile(book).upBookInfo() } } From 69dfea021828b7dd8bd0c3bfbc7552bccf8d6dfa Mon Sep 17 00:00:00 2001 From: Xwite <1797350009@qq.com> Date: Thu, 14 Apr 2022 12:36:20 +0800 Subject: [PATCH 2/6] perf:check GlideUrl cacheKey before call AnalyzeUrl --- .../java/io/legado/app/help/glide/OkHttpModelLoader.kt | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/io/legado/app/help/glide/OkHttpModelLoader.kt b/app/src/main/java/io/legado/app/help/glide/OkHttpModelLoader.kt index df822a618..e7dc687f0 100644 --- a/app/src/main/java/io/legado/app/help/glide/OkHttpModelLoader.kt +++ b/app/src/main/java/io/legado/app/help/glide/OkHttpModelLoader.kt @@ -5,6 +5,7 @@ import com.bumptech.glide.load.Options import com.bumptech.glide.load.model.GlideUrl import com.bumptech.glide.load.model.ModelLoader import io.legado.app.model.analyzeRule.AnalyzeUrl +import io.legado.app.utils.isAbsUrl import java.io.InputStream @@ -19,7 +20,11 @@ object OkHttpModelLoader : ModelLoader { height: Int, options: Options ): ModelLoader.LoadData { - val modelWithHeader = AnalyzeUrl(model.toString()).getGlideUrl() + val cacheKey = model.toString() + var modelWithHeader = model + if (cacheKey.isAbsUrl()) { + modelWithHeader = AnalyzeUrl(cacheKey).getGlideUrl() + } return ModelLoader.LoadData(modelWithHeader, OkHttpStreamFetcher(modelWithHeader, options)) } From cfa98112cecc5cd513bc292f9093ab6fa9a71cfa Mon Sep 17 00:00:00 2001 From: Xwite <1797350009@qq.com> Date: Thu, 14 Apr 2022 13:02:08 +0800 Subject: [PATCH 3/6] perf(AnalyzeUrl):skip init block when mUrl is DataURL --- .../io/legado/app/help/glide/ImageLoader.kt | 5 +++-- .../app/model/analyzeRule/AnalyzeUrl.kt | 22 ++++++++++--------- .../io/legado/app/utils/StringExtensions.kt | 6 +++++ 3 files changed, 21 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/io/legado/app/help/glide/ImageLoader.kt b/app/src/main/java/io/legado/app/help/glide/ImageLoader.kt index 4b0616d95..4bfc46d30 100644 --- a/app/src/main/java/io/legado/app/help/glide/ImageLoader.kt +++ b/app/src/main/java/io/legado/app/help/glide/ImageLoader.kt @@ -6,8 +6,8 @@ import android.graphics.drawable.Drawable import android.net.Uri import androidx.annotation.DrawableRes import com.bumptech.glide.RequestBuilder -import io.legado.app.constant.AppPattern.dataUriRegex import io.legado.app.utils.isAbsUrl +import io.legado.app.utils.isDataUrl import io.legado.app.utils.isContentScheme import java.io.File @@ -20,7 +20,7 @@ object ImageLoader { fun load(context: Context, path: String?): RequestBuilder { return when { path.isNullOrEmpty() -> GlideApp.with(context).load(path) - dataUriRegex.find(path) != null -> GlideApp.with(context).load(path) + path.isDataUrl() -> GlideApp.with(context).load(path) path.isAbsUrl() -> GlideApp.with(context).load(path) path.isContentScheme() -> GlideApp.with(context).load(Uri.parse(path)) else -> kotlin.runCatching { @@ -34,6 +34,7 @@ object ImageLoader { fun loadBitmap(context: Context, path: String?): RequestBuilder { return when { path.isNullOrEmpty() -> GlideApp.with(context).asBitmap().load(path) + path.isDataUrl() -> GlideApp.with(context).asBitmap().load(path) path.isAbsUrl() -> GlideApp.with(context).asBitmap().load(path) path.isContentScheme() -> GlideApp.with(context).asBitmap().load(Uri.parse(path)) else -> kotlin.runCatching { diff --git a/app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeUrl.kt b/app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeUrl.kt index dbf8f9b98..20aeae31a 100644 --- a/app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeUrl.kt +++ b/app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeUrl.kt @@ -71,16 +71,18 @@ class AnalyzeUrl( private var webJs: String? = null init { - val urlMatcher = paramPattern.matcher(baseUrl) - if (urlMatcher.find()) baseUrl = baseUrl.substring(0, urlMatcher.start()) - (headerMapF ?: source?.getHeaderMap(true))?.let { - headerMap.putAll(it) - if (it.containsKey("proxy")) { - proxy = it["proxy"] - headerMap.remove("proxy") + if (!mUrl.isDataUrl()) { + val urlMatcher = paramPattern.matcher(baseUrl) + if (urlMatcher.find()) baseUrl = baseUrl.substring(0, urlMatcher.start()) + (headerMapF ?: source?.getHeaderMap(true))?.let { + headerMap.putAll(it) + if (it.containsKey("proxy")) { + proxy = it["proxy"] + headerMap.remove("proxy") + } } + initUrl() } - initUrl() } /** @@ -457,10 +459,9 @@ class AnalyzeUrl( * 访问网站,返回ByteArray */ suspend fun getByteArrayAwait(): ByteArray { + val concurrentRecord = fetchStart() @Suppress("RegExpRedundantEscape") val dataUriFindResult = dataUriRegex.find(urlNoQuery) - val concurrentRecord = fetchStart() - setCookie(source?.getKey()) @Suppress("BlockingMethodInNonBlockingContext") if (dataUriFindResult != null) { val dataUriBase64 = dataUriFindResult.groupValues[1] @@ -468,6 +469,7 @@ class AnalyzeUrl( fetchEnd(concurrentRecord) return byteArray } else { + setCookie(source?.getKey()) val byteArray = getProxyClient(proxy).newCallResponseBody(retry) { addHeaders(headerMap) when (method) { diff --git a/app/src/main/java/io/legado/app/utils/StringExtensions.kt b/app/src/main/java/io/legado/app/utils/StringExtensions.kt index f8af7ee9a..a5a6c3f39 100644 --- a/app/src/main/java/io/legado/app/utils/StringExtensions.kt +++ b/app/src/main/java/io/legado/app/utils/StringExtensions.kt @@ -2,6 +2,7 @@ package io.legado.app.utils +import io.legado.app.constant.AppPattern.dataUriRegex import android.icu.text.Collator import android.icu.util.ULocale import android.net.Uri @@ -25,6 +26,11 @@ fun String?.isAbsUrl() = it.startsWith("http://", true) || it.startsWith("https://", true) } ?: false +fun String?.isDataUrl() = + this?.let { + dataUriRegex.matches(it) + } ?: false + fun String?.isJson(): Boolean = this?.run { val str = this.trim() From b7d1236069674561b16865797e18101c95e8beeb Mon Sep 17 00:00:00 2001 From: Xwite <1797350009@qq.com> Date: Thu, 14 Apr 2022 17:28:18 +0800 Subject: [PATCH 4/6] feat:add LruCache https://developer.android.google.cn/reference/kotlin/androidx/collection/LruCache --- .../book/read/page/provider/ImageProvider.kt | 49 ++++++++++++++++++- 1 file changed, 47 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/provider/ImageProvider.kt b/app/src/main/java/io/legado/app/ui/book/read/page/provider/ImageProvider.kt index 147d33e41..af6cbed8d 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/provider/ImageProvider.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/provider/ImageProvider.kt @@ -3,6 +3,7 @@ package io.legado.app.ui.book.read.page.provider import android.graphics.Bitmap import android.graphics.BitmapFactory import android.util.Size +import androidx.collection.LruCache import io.legado.app.R import io.legado.app.constant.AppLog.putDebug import io.legado.app.data.entities.Book @@ -23,6 +24,33 @@ object ImageProvider { BitmapFactory.decodeResource(appCtx.resources, R.drawable.image_loading_error) } + /** + *缓存bitmap LruCache实现 + */ + //private val maxMemory = Runtime.getRuntime().maxMemory() + //private val cacheMemorySize = (maxMemory / 8) as Int + private val cacheMemorySize: Int = 1024 * 1024 * 1024 //1G + private val bitmapLruCache = object : LruCache(cacheMemorySize) { + override fun sizeOf(key: String, bitmap: Bitmap): Int { + return bitmap.getByteCount() + } + override fun entryRemoved( + evicted: Boolean, + key: String, + oldBitmap: Bitmap, + newBitmap: Bitmap? + ) { + if (evicted) { + oldBitmap.recycle() + putDebug("自动回收Bitmap path: $key") + putDebug("bitmapLruCache : ${size()} / ${maxSize()}") + } + } + } + + /** + *缓存网络图片和epub图片 + */ suspend fun cacheImage( book: Book, src: String, @@ -45,6 +73,9 @@ object ImageProvider { return vFile } + /** + *获取图片宽度高度信息 + */ suspend fun getImageSize( book: Book, src: String, @@ -58,16 +89,23 @@ object ImageProvider { return Size(op.outWidth, op.outHeight) } + /** + *获取bitmap 使用LruCache缓存 + */ fun getImage( book: Book, src: String, width: Int, height: Int ): Bitmap { + val cacheBitmap = bitmapLruCache.get(src) + if (cacheBitmap != null) return cacheBitmap val vFile = BookHelp.getImage(book, src) @Suppress("BlockingMethodInNonBlockingContext") return try { - BitmapUtils.decodeBitmap(vFile.absolutePath, width, height) + val bitmap = BitmapUtils.decodeBitmap(vFile.absolutePath, width, height) + bitmapLruCache.put(src, bitmap) + bitmap } catch (e: Exception) { Coroutine.async { putDebug("${vFile.absolutePath} 解码失败\n$e", e) @@ -80,15 +118,22 @@ object ImageProvider { } } + /** + *获取bitmap 使用LruCache缓存 + */ fun getImage( book: Book, src: String, width: Int ): Bitmap { + val cacheBitmap = bitmapLruCache.get(src) + if (cacheBitmap != null) return cacheBitmap val vFile = BookHelp.getImage(book, src) @Suppress("BlockingMethodInNonBlockingContext") return try { - BitmapUtils.decodeBitmap(vFile.absolutePath, width) + val bitmap = BitmapUtils.decodeBitmap(vFile.absolutePath, width) + bitmapLruCache.put(src, bitmap) + bitmap } catch (e: Exception) { Coroutine.async { putDebug("${vFile.absolutePath} 解码失败\n$e", e) From 236aa2bf24a06bab8007b68f4e974a82e1abb133 Mon Sep 17 00:00:00 2001 From: Xwite <1797350009@qq.com> Date: Thu, 14 Apr 2022 19:12:53 +0800 Subject: [PATCH 5/6] refactor(BitmapUtils.kt):minify code --- .../java/io/legado/app/utils/BitmapUtils.kt | 79 ++++++------------- 1 file changed, 25 insertions(+), 54 deletions(-) diff --git a/app/src/main/java/io/legado/app/utils/BitmapUtils.kt b/app/src/main/java/io/legado/app/utils/BitmapUtils.kt index 1a276da89..797de8008 100644 --- a/app/src/main/java/io/legado/app/utils/BitmapUtils.kt +++ b/app/src/main/java/io/legado/app/utils/BitmapUtils.kt @@ -27,7 +27,7 @@ object BitmapUtils { * @return */ @Throws(IOException::class) - fun decodeBitmap(path: String, width: Int, height: Int): Bitmap { + fun decodeBitmap(path: String, width: Int, height: Int? = null): Bitmap { val fis = FileInputStream(path) return fis.use { @@ -35,44 +35,35 @@ object BitmapUtils { // inJustDecodeBounds如果设置为true,仅仅返回图片实际的宽和高,宽和高是赋值给opts.outWidth,opts.outHeight; op.inJustDecodeBounds = true BitmapFactory.decodeFileDescriptor(fis.fd, null, op) - //获取比例大小 - val wRatio = ceil((op.outWidth / width).toDouble()).toInt() - val hRatio = ceil((op.outHeight / height).toDouble()).toInt() - //如果超出指定大小,则缩小相应的比例 - if (wRatio > 1 && hRatio > 1) { - if (wRatio > hRatio) { - op.inSampleSize = wRatio - } else { - op.inSampleSize = hRatio - } - } + op.inSampleSize = calculateInSampleSize(op, width, height) op.inJustDecodeBounds = false BitmapFactory.decodeFileDescriptor(fis.fd, null, op) } } - @Throws(IOException::class) - fun decodeBitmap(path: String, width: Int): Bitmap { - - val fis = FileInputStream(path) - - return fis.use { - val op = BitmapFactory.Options() - // inJustDecodeBounds如果设置为true,仅仅返回图片实际的宽和高,宽和高是赋值给opts.outWidth,opts.outHeight; - op.inJustDecodeBounds = true - - BitmapFactory.decodeFileDescriptor(fis.fd, null, op) - //获取比例大小 - val wRatio = ceil((op.outWidth / width).toDouble()).toInt() - //如果超出指定大小,则缩小相应的比例 - if (wRatio > 1) { - op.inSampleSize = wRatio - } - op.inJustDecodeBounds = false - BitmapFactory.decodeFileDescriptor(fis.fd, null, op) + /** + *计算 InSampleSize。缺省返回1 + * @param options BitmapFactory.Options, + * @param width 想要显示的图片的宽度 + * @param height 想要显示的图片的高度 + * @return + */ + private fun calculateInSampleSize( + options: BitmapFactory.Options, + width: Int? = null, + height: Int? = null + ): Int { + //获取比例大小 + val wRatio = width?.let { ceil((options.outWidth / it).toDouble()).toInt() } ?: -1 + val hRatio = height?.let { ceil((options.outHeight / it).toDouble()).toInt() } ?: -1 + //如果超出指定大小,则缩小相应的比例 + return when { + wRatio > 1 && hRatio > 1 -> max(wRatio, hRatio) + wRatio > 1 -> wRatio + hRatio > 1 -> hRatio + else -> 1 } - } /** 从path中获取Bitmap图片 @@ -118,17 +109,7 @@ object BitmapUtils { // inJustDecodeBounds如果设置为true,仅仅返回图片实际的宽和高,宽和高是赋值给opts.outWidth,opts.outHeight; op.inJustDecodeBounds = true BitmapFactory.decodeResource(context.resources, resId, op) //获取尺寸信息 - //获取比例大小 - val wRatio = ceil((op.outWidth / width).toDouble()).toInt() - val hRatio = ceil((op.outHeight / height).toDouble()).toInt() - //如果超出指定大小,则缩小相应的比例 - if (wRatio > 1 && hRatio > 1) { - if (wRatio > hRatio) { - op.inSampleSize = wRatio - } else { - op.inSampleSize = hRatio - } - } + op.inSampleSize = calculateInSampleSize(op, width, height) op.inJustDecodeBounds = false return BitmapFactory.decodeResource(context.resources, resId, op) } @@ -154,17 +135,7 @@ object BitmapUtils { // inJustDecodeBounds如果设置为true,仅仅返回图片实际的宽和高,宽和高是赋值给opts.outWidth,opts.outHeight; op.inJustDecodeBounds = true BitmapFactory.decodeStream(inputStream, null, op) //获取尺寸信息 - //获取比例大小 - val wRatio = ceil((op.outWidth / width).toDouble()).toInt() - val hRatio = ceil((op.outHeight / height).toDouble()).toInt() - //如果超出指定大小,则缩小相应的比例 - if (wRatio > 1 && hRatio > 1) { - if (wRatio > hRatio) { - op.inSampleSize = wRatio - } else { - op.inSampleSize = hRatio - } - } + op.inSampleSize = calculateInSampleSize(op, width, height) inputStream = context.assets.open(fileNameInAssets) op.inJustDecodeBounds = false BitmapFactory.decodeStream(inputStream, null, op) From dd400833564571d4ca2f54c7180389f79e1e8776 Mon Sep 17 00:00:00 2001 From: Xwite <1797350009@qq.com> Date: Thu, 14 Apr 2022 19:29:54 +0800 Subject: [PATCH 6/6] refactor(ImageProvider.kt):minify code --- .../book/read/page/provider/ImageProvider.kt | 30 +------------------ 1 file changed, 1 insertion(+), 29 deletions(-) diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/provider/ImageProvider.kt b/app/src/main/java/io/legado/app/ui/book/read/page/provider/ImageProvider.kt index af6cbed8d..4cc9ad64c 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/provider/ImageProvider.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/provider/ImageProvider.kt @@ -96,7 +96,7 @@ object ImageProvider { book: Book, src: String, width: Int, - height: Int + height: Int? = null ): Bitmap { val cacheBitmap = bitmapLruCache.get(src) if (cacheBitmap != null) return cacheBitmap @@ -118,32 +118,4 @@ object ImageProvider { } } - /** - *获取bitmap 使用LruCache缓存 - */ - fun getImage( - book: Book, - src: String, - width: Int - ): Bitmap { - val cacheBitmap = bitmapLruCache.get(src) - if (cacheBitmap != null) return cacheBitmap - val vFile = BookHelp.getImage(book, src) - @Suppress("BlockingMethodInNonBlockingContext") - return try { - val bitmap = BitmapUtils.decodeBitmap(vFile.absolutePath, width) - bitmapLruCache.put(src, bitmap) - bitmap - } catch (e: Exception) { - Coroutine.async { - putDebug("${vFile.absolutePath} 解码失败\n$e", e) - if (FileUtils.readText(vFile.absolutePath).isXml()) { - putDebug("${vFile.absolutePath}为xml,自动删除") - vFile.delete() - } - } - errorBitmap - } - } - }