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/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)) } 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/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() } } 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 b6720cb2a..c490b210d 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, @@ -61,37 +92,23 @@ object ImageProvider { return Size(op.outWidth, op.outHeight) } + /** + *获取bitmap 使用LruCache缓存 + */ fun getImage( book: Book, src: String, width: Int, - height: Int + height: Int? = null ): 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) - } catch (e: Exception) { - Coroutine.async { - putDebug("${vFile.absolutePath} 解码失败\n$e", e) - if (FileUtils.readText(vFile.absolutePath).isXml()) { - putDebug("${vFile.absolutePath}为xml,自动删除") - vFile.delete() - } - } - errorBitmap - } - } - - fun getImage( - book: Book, - src: String, - width: Int - ): Bitmap { - val vFile = BookHelp.getImage(book, src) - @Suppress("BlockingMethodInNonBlockingContext") - return try { - BitmapUtils.decodeBitmap(vFile.absolutePath, width) + val bitmap = BitmapUtils.decodeBitmap(vFile.absolutePath, width, height) + bitmapLruCache.put(src, bitmap) + bitmap } catch (e: Exception) { Coroutine.async { putDebug("${vFile.absolutePath} 解码失败\n$e", e) 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) 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()