mirror of
https://github.com/gedoor/legado.git
synced 2025-08-10 00:52:30 +00:00
@@ -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
|
||||
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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user