Merge branch 'gedoor:master' into master

pull/1786/head
Xwite 3 years ago committed by GitHub
commit 2b81ca8fe5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 10
      app/src/main/java/io/legado/app/api/controller/BookController.kt
  2. 2
      app/src/main/java/io/legado/app/constant/AppLog.kt
  3. 9
      app/src/main/java/io/legado/app/data/entities/Book.kt
  4. 3
      app/src/main/java/io/legado/app/help/BookHelp.kt
  5. 44
      app/src/main/java/io/legado/app/help/http/cronet/AbsCallBack.kt
  6. 14
      app/src/main/java/io/legado/app/help/http/cronet/CronetHelper.kt
  7. 1
      app/src/main/java/io/legado/app/help/http/cronet/CronetInterceptor.kt
  8. 8
      app/src/main/java/io/legado/app/help/http/cronet/CronetLoader.kt
  9. 53
      app/src/main/java/io/legado/app/help/http/cronet/NewCallBack.kt
  10. 39
      app/src/main/java/io/legado/app/help/http/cronet/OldCallback.kt
  11. 18
      app/src/main/java/io/legado/app/ui/book/read/PhotoDialog.kt
  12. 53
      app/src/main/java/io/legado/app/ui/book/read/page/ContentTextView.kt
  13. 13
      app/src/main/java/io/legado/app/ui/book/read/page/provider/ChapterProvider.kt
  14. 60
      app/src/main/java/io/legado/app/ui/book/read/page/provider/ImageProvider.kt
  15. 11
      app/src/main/java/io/legado/app/utils/BitmapUtils.kt
  16. 4
      app/src/main/java/io/legado/app/utils/ThrowableExtensions.kt

@ -21,8 +21,8 @@ import io.legado.app.model.localBook.EpubFile
import io.legado.app.model.localBook.LocalBook import io.legado.app.model.localBook.LocalBook
import io.legado.app.model.localBook.UmdFile import io.legado.app.model.localBook.UmdFile
import io.legado.app.model.webBook.WebBook import io.legado.app.model.webBook.WebBook
import io.legado.app.utils.*
import io.legado.app.ui.book.read.page.provider.ImageProvider import io.legado.app.ui.book.read.page.provider.ImageProvider
import io.legado.app.utils.*
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import splitties.init.appCtx import splitties.init.appCtx
@ -87,9 +87,11 @@ object BookController {
this.bookSource = appDb.bookSourceDao.getBookSource(book.origin) this.bookSource = appDb.bookSourceDao.getBookSource(book.origin)
} }
this.bookUrl = bookUrl this.bookUrl = bookUrl
return ImageProvider.getImage(book, src, bookSource, width, width)?.let { val bitmap = runBlocking {
returnData.setData(it) ImageProvider.cacheImage(book, src, bookSource)
} ?: returnData.setErrorMsg("图片加载失败或不存在") ImageProvider.getImage(book, src, width, width)
}
return returnData.setData(bitmap)
} }
/** /**

@ -31,7 +31,7 @@ object AppLog {
} }
fun putDebug(message: String?, throwable: Throwable? = null) { fun putDebug(message: String?, throwable: Throwable? = null) {
if (AppConfig.recordLog) { if (AppConfig.recordLog || BuildConfig.DEBUG) {
put(message, throwable) put(message, throwable)
} }
} }

@ -193,7 +193,14 @@ data class Book(
} }
fun getUseReplaceRule(): Boolean { fun getUseReplaceRule(): Boolean {
return config.useReplaceRule ?: AppConfig.replaceEnableDefault val useReplaceRule = config.useReplaceRule
if (useReplaceRule != null) {
return useReplaceRule
}
if (type == BookType.image) {
return false
}
return AppConfig.replaceEnableDefault
} }
fun setReSegment(reSegment: Boolean) { fun setReSegment(reSegment: Boolean) {

@ -1,5 +1,6 @@
package io.legado.app.help package io.legado.app.help
import io.legado.app.constant.AppLog
import io.legado.app.constant.AppPattern import io.legado.app.constant.AppPattern
import io.legado.app.constant.EventBus import io.legado.app.constant.EventBus
import io.legado.app.data.appDb import io.legado.app.data.appDb
@ -126,7 +127,7 @@ object BookHelp {
).writeBytes(it) ).writeBytes(it)
} }
} catch (e: Exception) { } catch (e: Exception) {
e.printOnDebug() AppLog.putDebug("${src}下载错误", e)
} finally { } finally {
downloadImages.remove(src) downloadImages.remove(src)
} }

@ -2,7 +2,7 @@ package io.legado.app.help.http.cronet
import io.legado.app.help.http.okHttpClient import io.legado.app.help.http.okHttpClient
import io.legado.app.utils.DebugLog import io.legado.app.utils.DebugLog
import io.legado.app.utils.rethrowAsIOException import io.legado.app.utils.asIOException
import okhttp3.* import okhttp3.*
import okhttp3.EventListener import okhttp3.EventListener
import okhttp3.MediaType.Companion.toMediaTypeOrNull import okhttp3.MediaType.Companion.toMediaTypeOrNull
@ -27,12 +27,24 @@ abstract class AbsCallBack(
var mResponse: Response var mResponse: Response
var mException: IOException? = null
private var followCount = 0 private var followCount = 0
@Throws(IOException::class)
abstract fun waitForDone(urlRequest: UrlRequest): Response abstract fun waitForDone(urlRequest: UrlRequest): Response
/**
* 当发生错误时通知子类终止阻塞抛出错误
* @param error
*/
abstract fun onError(error: IOException)
/**
* 请求成功后通知子类结束阻塞返回response
* @param response
*/
abstract fun onSuccess(response: Response)
override fun onRedirectReceived( override fun onRedirectReceived(
request: UrlRequest, request: UrlRequest,
@ -41,10 +53,13 @@ abstract class AbsCallBack(
) { ) {
if (followCount > MAX_FOLLOW_COUNT) { if (followCount > MAX_FOLLOW_COUNT) {
request.cancel() request.cancel()
mException = IOException("Too many redirect") onError(IOException("Too many redirect"))
return
} }
if (mCall.isCanceled()) { if (mCall.isCanceled()) {
mException = IOException("Request Canceled") onError(IOException("Request Canceled"))
request.cancel()
return
} }
followCount += 1 followCount += 1
val client = okHttpClient val client = okHttpClient
@ -55,7 +70,7 @@ abstract class AbsCallBack(
} else if (okHttpClient.followRedirects) { } else if (okHttpClient.followRedirects) {
request.followRedirect() request.followRedirect()
} else { } else {
mException = IOException("Too many redirect") onError(IOException("Too many redirect"))
request.cancel() request.cancel()
} }
} }
@ -73,7 +88,7 @@ abstract class AbsCallBack(
} }
@Throws(Exception::class) @Throws(IOException::class)
override fun onReadCompleted( override fun onReadCompleted(
request: UrlRequest, request: UrlRequest,
info: UrlResponseInfo, info: UrlResponseInfo,
@ -83,7 +98,7 @@ abstract class AbsCallBack(
if (mCall.isCanceled()) { if (mCall.isCanceled()) {
request.cancel() request.cancel()
mException = IOException("Request Canceled") onError(IOException("Request Canceled"))
} }
byteBuffer.flip() byteBuffer.flip()
@ -91,9 +106,9 @@ abstract class AbsCallBack(
try { try {
buffer.write(byteBuffer) buffer.write(byteBuffer)
} catch (e: IOException) { } catch (e: IOException) {
DebugLog.i(javaClass.name, "IOException during ByteBuffer read. Details: ", e) DebugLog.e(javaClass.name, "IOException during ByteBuffer read. Details: ", e)
mException = IOException("IOException during ByteBuffer read. Details:", e) onError(IOException("IOException during ByteBuffer read. Details:", e))
throw e return
} }
byteBuffer.clear() byteBuffer.clear()
request.read(byteBuffer) request.read(byteBuffer)
@ -108,7 +123,8 @@ abstract class AbsCallBack(
buffer.asResponseBody(contentType) buffer.asResponseBody(contentType)
val newRequest = originalRequest.newBuilder().url(info.url).build() val newRequest = originalRequest.newBuilder().url(info.url).build()
this.mResponse = this.mResponse.newBuilder().body(responseBody).request(newRequest).build() this.mResponse = this.mResponse.newBuilder().body(responseBody).request(newRequest).build()
DebugLog.i(javaClass.simpleName, "end[${info.negotiatedProtocol}]${info.url}") onSuccess(this.mResponse)
//DebugLog.i(javaClass.simpleName, "end[${info.negotiatedProtocol}]${info.url}")
eventListener?.callEnd(mCall) eventListener?.callEnd(mCall)
if (responseCallback != null) { if (responseCallback != null) {
@ -123,8 +139,8 @@ abstract class AbsCallBack(
//UrlResponseInfo可能为null //UrlResponseInfo可能为null
override fun onFailed(request: UrlRequest, info: UrlResponseInfo?, error: CronetException) { override fun onFailed(request: UrlRequest, info: UrlResponseInfo?, error: CronetException) {
DebugLog.i(javaClass.name, error.message.toString()) DebugLog.e(javaClass.name, error.message.toString())
mException = error.rethrowAsIOException() onError(error.asIOException())
this.eventListener?.callFailed(mCall, error) this.eventListener?.callFailed(mCall, error)
responseCallback?.onFailure(mCall, error) responseCallback?.onFailure(mCall, error)
} }
@ -132,7 +148,7 @@ abstract class AbsCallBack(
override fun onCanceled(request: UrlRequest?, info: UrlResponseInfo?) { override fun onCanceled(request: UrlRequest?, info: UrlResponseInfo?) {
super.onCanceled(request, info) super.onCanceled(request, info)
this.eventListener?.callEnd(mCall) this.eventListener?.callEnd(mCall)
mException = IOException("Cronet Request Canceled") onError(IOException("Cronet Request Canceled"))
} }

@ -2,6 +2,7 @@ package io.legado.app.help.http.cronet
import io.legado.app.constant.AppLog import io.legado.app.constant.AppLog
import io.legado.app.help.config.AppConfig import io.legado.app.help.config.AppConfig
import io.legado.app.help.http.okHttpClient
import io.legado.app.utils.DebugLog import io.legado.app.utils.DebugLog
import okhttp3.Headers import okhttp3.Headers
import okhttp3.MediaType import okhttp3.MediaType
@ -13,11 +14,8 @@ import org.chromium.net.UploadDataProviders
import org.chromium.net.UrlRequest import org.chromium.net.UrlRequest
import splitties.init.appCtx import splitties.init.appCtx
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors
//val executor: ExecutorService by lazy { Executors.newCachedThreadPool() }
val executor: ExecutorService by lazy { Executors.newCachedThreadPool() }
val cronetEngine: ExperimentalCronetEngine? by lazy { val cronetEngine: ExperimentalCronetEngine? by lazy {
if (!AppConfig.isGooglePlay) { if (!AppConfig.isGooglePlay) {
@ -48,7 +46,11 @@ fun buildRequest(request: Request, callback: UrlRequest.Callback): UrlRequest? {
val url = request.url.toString() val url = request.url.toString()
val headers: Headers = request.headers val headers: Headers = request.headers
val requestBody = request.body val requestBody = request.body
return cronetEngine?.newUrlRequestBuilder(url, callback, executor)?.apply { return cronetEngine?.newUrlRequestBuilder(
url,
callback,
okHttpClient.dispatcher.executorService
)?.apply {
setHttpMethod(request.method)//设置 setHttpMethod(request.method)//设置
allowDirectExecutor() allowDirectExecutor()
headers.forEachIndexed { index, _ -> headers.forEachIndexed { index, _ ->
@ -65,7 +67,7 @@ fun buildRequest(request: Request, callback: UrlRequest.Callback): UrlRequest? {
requestBody.writeTo(buffer) requestBody.writeTo(buffer)
setUploadDataProvider( setUploadDataProvider(
UploadDataProviders.create(buffer.readByteArray()), UploadDataProviders.create(buffer.readByteArray()),
executor okHttpClient.dispatcher.executorService
) )
} }

@ -49,7 +49,6 @@ class CronetInterceptor(private val cookieJar: CookieJar?) : Interceptor {
OldCallback(request, call) OldCallback(request, call)
} }
buildRequest(request, callBack)?.let { buildRequest(request, callBack)?.let {
it.start()
return callBack.waitForDone(it) return callBack.waitForDone(it)
} }
return null return null

@ -268,7 +268,8 @@ object CronetLoader : CronetEngine.Builder.LibraryLoader() {
return return
} }
download = true download = true
executor.execute {
Coroutine.async {
val result = downloadFileIfNotExist(url, downloadTempFile) val result = downloadFileIfNotExist(url, downloadTempFile)
DebugLog.d(javaClass.simpleName, "download result:$result") DebugLog.d(javaClass.simpleName, "download result:$result")
//文件md5再次校验 //文件md5再次校验
@ -279,7 +280,7 @@ object CronetLoader : CronetEngine.Builder.LibraryLoader() {
downloadTempFile.deleteOnExit() downloadTempFile.deleteOnExit()
} }
download = false download = false
return@execute return@async
} }
DebugLog.d(javaClass.simpleName, "download success, copy to $destSuccessFile") DebugLog.d(javaClass.simpleName, "download success, copy to $destSuccessFile")
//下载成功拷贝文件 //下载成功拷贝文件
@ -289,6 +290,9 @@ object CronetLoader : CronetEngine.Builder.LibraryLoader() {
@Suppress("SameParameterValue") @Suppress("SameParameterValue")
deleteHistoryFile(parentFile!!, null) deleteHistoryFile(parentFile!!, null)
} }
// executor.execute {
//
// }
} }
/** /**

@ -6,11 +6,8 @@ import androidx.annotation.RequiresApi
import okhttp3.Call import okhttp3.Call
import okhttp3.Request import okhttp3.Request
import okhttp3.Response import okhttp3.Response
import org.chromium.net.CronetException
import org.chromium.net.UrlRequest import org.chromium.net.UrlRequest
import org.chromium.net.UrlResponseInfo
import java.io.IOException import java.io.IOException
import java.nio.ByteBuffer
import java.util.concurrent.CompletableFuture import java.util.concurrent.CompletableFuture
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
@ -21,45 +18,29 @@ class NewCallBack(originalRequest: Request, mCall: Call) : AbsCallBack(originalR
@Throws(IOException::class) @Throws(IOException::class)
override fun waitForDone(urlRequest: UrlRequest): Response { override fun waitForDone(urlRequest: UrlRequest): Response {
return responseFuture.get(mCall.timeout().timeoutNanos(), TimeUnit.NANOSECONDS) urlRequest.start()
return if (mCall.timeout().timeoutNanos() > 0) {
responseFuture.get(mCall.timeout().timeoutNanos(), TimeUnit.NANOSECONDS)
} else {
return responseFuture.get()
} }
override fun onRedirectReceived(
request: UrlRequest,
info: UrlResponseInfo,
newLocationUrl: String
) {
super.onRedirectReceived(request, info, newLocationUrl)
if (mException != null) {
responseFuture.completeExceptionally(mException)
}
}
override fun onReadCompleted(
request: UrlRequest,
info: UrlResponseInfo,
byteBuffer: ByteBuffer
) {
super.onReadCompleted(request, info, byteBuffer)
if (mException != null) {
responseFuture.completeExceptionally(mException)
}
}
override fun onSucceeded(request: UrlRequest, info: UrlResponseInfo) {
super.onSucceeded(request, info)
responseFuture.complete(mResponse)
} }
override fun onFailed(request: UrlRequest, info: UrlResponseInfo?, error: CronetException) { /**
super.onFailed(request, info, error) * 当发生错误时通知子类终止阻塞抛出错误
responseFuture.completeExceptionally(mException) * @param error
*/
override fun onError(error: IOException) {
responseFuture.completeExceptionally(error)
} }
override fun onCanceled(request: UrlRequest?, info: UrlResponseInfo?) { /**
super.onCanceled(request, info) * 请求成功后通知子类结束阻塞返回response
responseFuture.completeExceptionally(mException) * @param response
*/
override fun onSuccess(response: Response) {
responseFuture.complete(response)
} }

@ -4,20 +4,19 @@ import android.os.ConditionVariable
import okhttp3.Call import okhttp3.Call
import okhttp3.Request import okhttp3.Request
import okhttp3.Response import okhttp3.Response
import org.chromium.net.CronetException
import org.chromium.net.UrlRequest import org.chromium.net.UrlRequest
import org.chromium.net.UrlResponseInfo
import java.io.IOException import java.io.IOException
import java.nio.ByteBuffer
class OldCallback(originalRequest: Request, mCall: Call) : AbsCallBack(originalRequest, mCall) { class OldCallback(originalRequest: Request, mCall: Call) : AbsCallBack(originalRequest, mCall) {
private val mResponseCondition = ConditionVariable() private val mResponseCondition = ConditionVariable()
private var mException: IOException? = null
@Throws(IOException::class) @Throws(IOException::class)
override fun waitForDone(urlRequest: UrlRequest): Response { override fun waitForDone(urlRequest: UrlRequest): Response {
//获取okhttp call的完整请求的超时时间 //获取okhttp call的完整请求的超时时间
val timeOutMs: Long = mCall.timeout().timeoutNanos() / 1000000 val timeOutMs: Long = mCall.timeout().timeoutNanos() / 1000000
urlRequest.start()
if (timeOutMs > 0) { if (timeOutMs > 0) {
mResponseCondition.block(timeOutMs) mResponseCondition.block(timeOutMs)
} else { } else {
@ -32,35 +31,25 @@ class OldCallback(originalRequest: Request, mCall: Call) : AbsCallBack(originalR
if (mException != null) { if (mException != null) {
throw mException as IOException throw mException as IOException
} }
return this.mResponse return mResponse
} }
/**
override fun onReadCompleted( * 当发生错误时通知子类终止阻塞抛出错误
request: UrlRequest, * @param error
info: UrlResponseInfo, */
byteBuffer: ByteBuffer override fun onError(error: IOException) {
) { mException = error
super.onReadCompleted(request, info, byteBuffer)
if (mException != null) {
mResponseCondition.open() mResponseCondition.open()
} }
}
override fun onSucceeded(request: UrlRequest, info: UrlResponseInfo) { /**
super.onSucceeded(request, info) * 请求成功后通知子类结束阻塞返回response
* @param response
*/
override fun onSuccess(response: Response) {
mResponseCondition.open() mResponseCondition.open()
} }
override fun onFailed(request: UrlRequest, info: UrlResponseInfo?, error: CronetException) {
super.onFailed(request, info, error)
mResponseCondition.open()
}
override fun onCanceled(request: UrlRequest?, info: UrlResponseInfo?) {
super.onCanceled(request, info)
mResponseCondition.open()
}
} }

@ -5,9 +5,10 @@ import android.view.View
import io.legado.app.R import io.legado.app.R
import io.legado.app.base.BaseDialogFragment import io.legado.app.base.BaseDialogFragment
import io.legado.app.databinding.DialogPhotoViewBinding import io.legado.app.databinding.DialogPhotoViewBinding
import io.legado.app.help.BookHelp
import io.legado.app.help.glide.ImageLoader
import io.legado.app.model.BookCover import io.legado.app.model.BookCover
import io.legado.app.model.ReadBook import io.legado.app.model.ReadBook
import io.legado.app.ui.book.read.page.provider.ImageProvider
import io.legado.app.utils.setLayout import io.legado.app.utils.setLayout
import io.legado.app.utils.viewbindingdelegate.viewBinding import io.legado.app.utils.viewbindingdelegate.viewBinding
@ -40,17 +41,12 @@ class PhotoDialog() : BaseDialogFragment(R.layout.dialog_photo_view) {
arguments?.let { arguments?.let {
val path = it.getString("path") val path = it.getString("path")
if (path.isNullOrEmpty()) { if (path.isNullOrEmpty()) {
val chapterIndex = it.getInt("chapterIndex")
val src = it.getString("src")
ReadBook.book?.let { book -> ReadBook.book?.let { book ->
src?.let { it.getString("src")?.let { src ->
execute { val file = BookHelp.getImage(book, src)
ImageProvider.getImage(book, src, ReadBook.bookSource) ImageLoader.load(requireContext(), file)
}.onSuccess { bitmap -> .error(R.drawable.image_loading_error)
if (bitmap != null) { .into(binding.photoView)
binding.photoView.setImageBitmap(bitmap)
}
}
} }
} }
} else { } else {

@ -82,43 +82,36 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
private fun drawPage(canvas: Canvas) { private fun drawPage(canvas: Canvas) {
var relativeOffset = relativeOffset(0) var relativeOffset = relativeOffset(0)
textPage.textLines.forEach { textLine -> textPage.textLines.forEach { textLine ->
draw(canvas, textLine, relativeOffset) draw(canvas, textPage, textLine, relativeOffset)
} }
if (!callBack.isScroll) return if (!callBack.isScroll) return
//滚动翻页 //滚动翻页
if (!pageFactory.hasNext()) return if (!pageFactory.hasNext()) return
val nextPage = relativePage(1) val textPage1 = relativePage(1)
relativeOffset = relativeOffset(1) relativeOffset = relativeOffset(1)
nextPage.textLines.forEach { textLine -> textPage1.textLines.forEach { textLine ->
draw(canvas, textLine, relativeOffset) draw(canvas, textPage1, textLine, relativeOffset)
} }
if (!pageFactory.hasNextPlus()) return if (!pageFactory.hasNextPlus()) return
relativeOffset = relativeOffset(2) relativeOffset = relativeOffset(2)
if (relativeOffset < ChapterProvider.visibleHeight) { if (relativeOffset < ChapterProvider.visibleHeight) {
relativePage(2).textLines.forEach { textLine -> val textPage2 = relativePage(2)
draw(canvas, textLine, relativeOffset) textPage2.textLines.forEach { textLine ->
draw(canvas, textPage2, textLine, relativeOffset)
} }
} }
} }
private fun draw( private fun draw(
canvas: Canvas, canvas: Canvas,
textPage: TextPage,
textLine: TextLine, textLine: TextLine,
relativeOffset: Float, relativeOffset: Float,
) { ) {
val lineTop = textLine.lineTop + relativeOffset val lineTop = textLine.lineTop + relativeOffset
val lineBase = textLine.lineBase + relativeOffset val lineBase = textLine.lineBase + relativeOffset
val lineBottom = textLine.lineBottom + relativeOffset val lineBottom = textLine.lineBottom + relativeOffset
drawChars( drawChars(canvas, textPage, textLine, lineTop, lineBase, lineBottom)
canvas,
textLine.textChars,
lineTop,
lineBase,
lineBottom,
textLine.isTitle,
textLine.isReadAloud,
textLine.isImage
)
} }
/** /**
@ -126,23 +119,21 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
*/ */
private fun drawChars( private fun drawChars(
canvas: Canvas, canvas: Canvas,
textChars: List<TextChar>, textPage: TextPage,
textLine: TextLine,
lineTop: Float, lineTop: Float,
lineBase: Float, lineBase: Float,
lineBottom: Float, lineBottom: Float,
isTitle: Boolean,
isReadAloud: Boolean,
isImageLine: Boolean
) { ) {
val textPaint = if (isTitle) { val textPaint = if (textLine.isTitle) {
ChapterProvider.titlePaint ChapterProvider.titlePaint
} else { } else {
ChapterProvider.contentPaint ChapterProvider.contentPaint
} }
val textColor = if (isReadAloud) context.accentColor else ReadBookConfig.textColor val textColor = if (textLine.isReadAloud) context.accentColor else ReadBookConfig.textColor
textChars.forEach { textLine.textChars.forEach {
if (it.isImage) { if (it.isImage) {
drawImage(canvas, it, lineTop, lineBottom, isImageLine) drawImage(canvas, textPage, textLine, it, lineTop, lineBottom)
} else { } else {
textPaint.color = textColor textPaint.color = textColor
if (it.isSearchResult) { if (it.isSearchResult) {
@ -159,22 +150,23 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
/** /**
* 绘制图片 * 绘制图片
*/ */
@Suppress("UNUSED_PARAMETER")
private fun drawImage( private fun drawImage(
canvas: Canvas, canvas: Canvas,
textPage: TextPage,
textLine: TextLine,
textChar: TextChar, textChar: TextChar,
lineTop: Float, lineTop: Float,
lineBottom: Float, lineBottom: Float
isImageLine: Boolean
) { ) {
val book = ReadBook.book ?: return val book = ReadBook.book ?: return
ImageProvider.getImage( val bitmap = ImageProvider.getImage(
book, book,
textChar.charData, textChar.charData,
ReadBook.bookSource,
(textChar.end - textChar.start).toInt(), (textChar.end - textChar.start).toInt(),
(lineBottom - lineTop).toInt() (lineBottom - lineTop).toInt()
)?.let { bitmap -> )
val rectF = if (isImageLine) { val rectF = if (textLine.isImage) {
RectF(textChar.start, lineTop, textChar.end, lineBottom) RectF(textChar.start, lineTop, textChar.end, lineBottom)
} else { } else {
/*以宽度为基准保持图片的原始比例叠加,当div为负数时,允许高度比字符更高*/ /*以宽度为基准保持图片的原始比例叠加,当div为负数时,允许高度比字符更高*/
@ -188,7 +180,6 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
context.toastOnUi(e.localizedMessage) context.toastOnUi(e.localizedMessage)
} }
} }
}
/** /**
* 滚动事件 * 滚动事件

@ -208,22 +208,23 @@ object ChapterProvider {
imageStyle: String?, imageStyle: String?,
): Float { ): Float {
var durY = y var durY = y
ImageProvider.getImageSize(book, src, ReadBook.bookSource).let { val size = ImageProvider.getImageSize(book, src, ReadBook.bookSource)
if (size.width > 0 && size.height > 0) {
if (durY > visibleHeight) { if (durY > visibleHeight) {
textPages.last().height = durY textPages.last().height = durY
textPages.add(TextPage()) textPages.add(TextPage())
durY = 0f durY = 0f
} }
var height = it.height var height = size.height
var width = it.width var width = size.width
when (imageStyle?.toUpperCase(Locale.ROOT)) { when (imageStyle?.toUpperCase(Locale.ROOT)) {
Book.imgStyleFull -> { Book.imgStyleFull -> {
width = visibleWidth width = visibleWidth
height = it.height * visibleWidth / it.width height = size.height * visibleWidth / size.width
} }
else -> { else -> {
if (it.width > visibleWidth) { if (size.width > visibleWidth) {
height = it.height * visibleWidth / it.width height = size.height * visibleWidth / size.width
width = visibleWidth width = visibleWidth
} }
if (height > visibleHeight) { if (height > visibleHeight) {

@ -9,16 +9,13 @@ import io.legado.app.data.entities.Book
import io.legado.app.data.entities.BookSource import io.legado.app.data.entities.BookSource
import io.legado.app.help.BookHelp import io.legado.app.help.BookHelp
import io.legado.app.help.coroutine.Coroutine import io.legado.app.help.coroutine.Coroutine
import io.legado.app.help.glide.ImageLoader
import io.legado.app.model.localBook.EpubFile import io.legado.app.model.localBook.EpubFile
import io.legado.app.utils.BitmapUtils
import io.legado.app.utils.FileUtils import io.legado.app.utils.FileUtils
import io.legado.app.utils.isXml import io.legado.app.utils.isXml
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.suspendCancellableCoroutine
import splitties.init.appCtx import splitties.init.appCtx
import java.io.File import java.io.File
import java.io.FileOutputStream import java.io.FileOutputStream
import kotlin.coroutines.resume
object ImageProvider { object ImageProvider {
@ -26,7 +23,7 @@ object ImageProvider {
BitmapFactory.decodeResource(appCtx.resources, R.drawable.image_loading_error) BitmapFactory.decodeResource(appCtx.resources, R.drawable.image_loading_error)
} }
private suspend fun cacheImage( suspend fun cacheImage(
book: Book, book: Book,
src: String, src: String,
bookSource: BookSource? bookSource: BookSource?
@ -54,59 +51,26 @@ object ImageProvider {
bookSource: BookSource? bookSource: BookSource?
): Size { ): Size {
val file = cacheImage(book, src, bookSource) val file = cacheImage(book, src, bookSource)
return suspendCancellableCoroutine { block -> val op = BitmapFactory.Options()
kotlin.runCatching { // inJustDecodeBounds如果设置为true,仅仅返回图片实际的宽和高,宽和高是赋值给opts.outWidth,opts.outHeight;
ImageLoader.loadBitmap(appCtx, file.absolutePath).submit() op.inJustDecodeBounds = true
.getSize { width, height -> BitmapFactory.decodeFile(file.absolutePath, op)
block.resume(Size(width, height)) return Size(op.outWidth, op.outHeight)
}
}.onFailure {
block.resume(Size(errorBitmap.width, errorBitmap.height))
}
}
} }
fun getImage( fun getImage(
book: Book, book: Book,
src: String, src: String,
bookSource: BookSource?,
width: Int, width: Int,
height: Int height: Int
): Bitmap? { ): Bitmap {
val vFile = runBlocking { val vFile = BookHelp.getImage(book, src)
cacheImage(book, src, bookSource) @Suppress("BlockingMethodInNonBlockingContext")
}
return try {
ImageLoader.loadBitmap(appCtx, vFile.absolutePath)
.submit(width, height)
.get()
} catch (e: Exception) {
Coroutine.async {
putDebug("${vFile.absolutePath} 解码失败\n${e.toString()}", e)
if (FileUtils.readText(vFile.absolutePath).isXml()) {
putDebug("${vFile.absolutePath}为xml,自动删除")
vFile.delete()
}
}
errorBitmap
}
}
fun getImage(
book: Book,
src: String,
bookSource: BookSource?
): Bitmap? {
val vFile = runBlocking {
cacheImage(book, src, bookSource)
}
return try { return try {
ImageLoader.loadBitmap(appCtx, vFile.absolutePath) BitmapUtils.decodeBitmap(vFile.absolutePath, width, height)
.submit(ChapterProvider.visibleWidth, ChapterProvider.visibleHeight)
.get()
} catch (e: Exception) { } catch (e: Exception) {
Coroutine.async { Coroutine.async {
putDebug("${vFile.absolutePath} 解码失败\n${e.toString()}", e) putDebug("${vFile.absolutePath} 解码失败\n$e", e)
if (FileUtils.readText(vFile.absolutePath).isXml()) { if (FileUtils.readText(vFile.absolutePath).isXml()) {
putDebug("${vFile.absolutePath}为xml,自动删除") putDebug("${vFile.absolutePath}为xml,自动删除")
vFile.delete() vFile.delete()

@ -7,7 +7,6 @@ import android.graphics.*
import android.graphics.Bitmap.Config import android.graphics.Bitmap.Config
import android.view.View import android.view.View
import com.google.android.renderscript.Toolkit import com.google.android.renderscript.Toolkit
import java.io.FileInputStream
import java.io.IOException import java.io.IOException
import kotlin.math.* import kotlin.math.*
@ -27,10 +26,9 @@ object BitmapUtils {
@Throws(IOException::class) @Throws(IOException::class)
fun decodeBitmap(path: String, width: Int, height: Int): Bitmap { fun decodeBitmap(path: String, width: Int, height: Int): Bitmap {
val op = BitmapFactory.Options() val op = BitmapFactory.Options()
val ips = FileInputStream(path)
// inJustDecodeBounds如果设置为true,仅仅返回图片实际的宽和高,宽和高是赋值给opts.outWidth,opts.outHeight; // inJustDecodeBounds如果设置为true,仅仅返回图片实际的宽和高,宽和高是赋值给opts.outWidth,opts.outHeight;
op.inJustDecodeBounds = true op.inJustDecodeBounds = true
BitmapFactory.decodeFileDescriptor(ips.fd, null, op) BitmapFactory.decodeFile(path, op)
//获取比例大小 //获取比例大小
val wRatio = ceil((op.outWidth / width).toDouble()).toInt() val wRatio = ceil((op.outWidth / width).toDouble()).toInt()
val hRatio = ceil((op.outHeight / height).toDouble()).toInt() val hRatio = ceil((op.outHeight / height).toDouble()).toInt()
@ -43,7 +41,7 @@ object BitmapUtils {
} }
} }
op.inJustDecodeBounds = false op.inJustDecodeBounds = false
return BitmapFactory.decodeFileDescriptor(ips.fd, null, op) return BitmapFactory.decodeFile(path, op)
} }
/** 从path中获取Bitmap图片 /** 从path中获取Bitmap图片
@ -53,12 +51,11 @@ object BitmapUtils {
@Throws(IOException::class) @Throws(IOException::class)
fun decodeBitmap(path: String): Bitmap { fun decodeBitmap(path: String): Bitmap {
val opts = BitmapFactory.Options() val opts = BitmapFactory.Options()
val ips = FileInputStream(path)
opts.inJustDecodeBounds = true opts.inJustDecodeBounds = true
BitmapFactory.decodeFileDescriptor(ips.fd, null, opts) BitmapFactory.decodeFile(path, opts)
opts.inSampleSize = computeSampleSize(opts, -1, 128 * 128) opts.inSampleSize = computeSampleSize(opts, -1, 128 * 128)
opts.inJustDecodeBounds = false opts.inJustDecodeBounds = false
return BitmapFactory.decodeFileDescriptor(ips.fd, null, opts) return BitmapFactory.decodeFile(path, opts)
} }
/** /**

@ -12,8 +12,8 @@ val Throwable.msg: String
} }
} }
fun Throwable.rethrowAsIOException(): IOException { fun Throwable.asIOException(): IOException {
val newException = IOException(this.message) val newException = IOException(this.message)
newException.initCause(this) newException.initCause(this)
throw newException return newException
} }
Loading…
Cancel
Save