Merge pull request #1786 from Xwite/master

缓存正文bitmap;其他优化
pull/1787/head
kunfei 3 years ago committed by GitHub
commit a473582ea1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 5
      app/src/main/java/io/legado/app/help/glide/ImageLoader.kt
  2. 7
      app/src/main/java/io/legado/app/help/glide/OkHttpModelLoader.kt
  3. 22
      app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeUrl.kt
  4. 21
      app/src/main/java/io/legado/app/model/localBook/BaseLocalBookParse.kt
  5. 10
      app/src/main/java/io/legado/app/model/localBook/EpubFile.kt
  6. 7
      app/src/main/java/io/legado/app/model/localBook/README.md
  7. 10
      app/src/main/java/io/legado/app/model/localBook/UmdFile.kt
  8. 63
      app/src/main/java/io/legado/app/ui/book/read/page/provider/ImageProvider.kt
  9. 79
      app/src/main/java/io/legado/app/utils/BitmapUtils.kt
  10. 6
      app/src/main/java/io/legado/app/utils/StringExtensions.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<Drawable> {
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<Bitmap> {
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 {

@ -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<GlideUrl?, InputStream?> {
height: Int,
options: Options
): ModelLoader.LoadData<InputStream?> {
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))
}

@ -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) {

@ -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<BookChapter>
fun getContent(book: Book, chapter: BookChapter): String?
fun getImage(book: Book, href: String): InputStream?
}

@ -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<BookChapter> {
override fun getChapterList(book: Book): ArrayList<BookChapter> {
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()
}
}

@ -0,0 +1,7 @@
# 本地书籍解析
* BaseLocalBookParse.kt 本地书籍解析接口
* LocalBook.kt 总入口
* TextFile.kt 解析txt
* EpubFile.kt 解析epub
* UmdFile.kt 解析umd

@ -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<BookChapter> {
override fun getChapterList(book: Book): ArrayList<BookChapter> {
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()
}
}

@ -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<String, Bitmap>(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
): Bitmap {
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
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)
val bitmap = BitmapUtils.decodeBitmap(vFile.absolutePath, width, height)
bitmapLruCache.put(src, bitmap)
bitmap
} catch (e: Exception) {
Coroutine.async {
putDebug("${vFile.absolutePath} 解码失败\n$e", e)

@ -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)

@ -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()

Loading…
Cancel
Save