epub读取优化&Bitmap解码优化

导入epub优化,复制文件到缓存区域,使用ZipFile代替ZipInputStream,使得读取能懒加载epub,减少读取epub时的内存占用。
使用BitmapFactory.decodeStream代替BitmapFactory.decodeFile,来减少加载图片时产生OOM的可能性。
pull/957/head
ag2s20150909 4 years ago
parent a4ffeea3d5
commit 77a1dbe7f4
  1. 4
      app/src/main/java/io/legado/app/help/BookHelp.kt
  2. 71
      app/src/main/java/io/legado/app/model/localBook/EpubFile.kt
  3. 4
      app/src/main/java/io/legado/app/ui/book/read/page/provider/ImageProvider.kt
  4. 18
      app/src/main/java/io/legado/app/utils/BitmapUtils.kt
  5. 38
      app/src/main/java/io/legado/app/utils/FileUtils.kt

@ -21,9 +21,9 @@ import kotlin.math.max
import kotlin.math.min
object BookHelp {
private const val cacheFolderName = "book_cache"
const val cacheFolderName = "book_cache"
private const val cacheImageFolderName = "images"
private val downloadDir: File = appCtx.externalFilesDir
val downloadDir: File = appCtx.externalFilesDir
private val downloadImages = CopyOnWriteArraySet<String>()
fun clearCache() {

@ -6,12 +6,10 @@ import android.net.Uri
import android.text.TextUtils
import io.legado.app.data.entities.Book
import io.legado.app.data.entities.BookChapter
import io.legado.app.help.BookHelp
import io.legado.app.utils.*
import me.ag2s.epublib.domain.EpubBook
import me.ag2s.epublib.domain.MediaTypes
import me.ag2s.epublib.domain.Resources
import me.ag2s.epublib.epub.EpubReader
import me.ag2s.epublib.util.ResourceUtil
import org.jsoup.Jsoup
import splitties.init.appCtx
import java.io.File
@ -20,8 +18,7 @@ import java.io.IOException
import java.io.InputStream
import java.nio.charset.Charset
import java.util.*
import java.util.zip.ZipEntry
import java.util.zip.ZipInputStream
import java.util.zip.ZipFile
class EpubFile(var book: Book) {
@ -101,32 +98,46 @@ class EpubFile(var book: Book) {
/*重写epub文件解析代码,直接读出压缩包文件生成Resources给epublib,这样的好处是可以逐一修改某些文件的格式错误*/
private fun readEpub(): EpubBook? {
try {
val input = if (book.bookUrl.isContentScheme()) {
val uri = Uri.parse(book.bookUrl)
appCtx.contentResolver.openInputStream(uri)
} else {
File(book.bookUrl).inputStream()
}
input ?: return null
val inZip = ZipInputStream(input)
var zipEntry: ZipEntry?
val resources = Resources()
do {
zipEntry = inZip.nextEntry
if ((zipEntry == null) || zipEntry.isDirectory || zipEntry == ZipEntry("<error>")) continue
val resource = ResourceUtil.createResource(zipEntry, inZip)
if (resource.mediaType == MediaTypes.XHTML) resource.inputEncoding = "UTF-8"
if (zipEntry.name.endsWith(".opf")) {
/*掌上书苑有很多自制书OPF的nameSpace格式不标准,强制修复成正确的格式*/
val newS = String(resource.data).replace(
"\\smlns=\"http://www.idpf.org/2007/opf\"".toRegex(),
" xmlns=\"http://www.idpf.org/2007/opf\""
)
resource.data = newS.toByteArray()
//f
val file = FileUtils.getFile(
BookHelp.downloadDir,
BookHelp.cacheFolderName,
book.getFolderName(),
"index.epubx"
)
if (!file.exists()) {
val input = if (book.bookUrl.isContentScheme()) {
val uri = Uri.parse(book.bookUrl)
appCtx.contentResolver.openInputStream(uri)
} else {
File(book.bookUrl).inputStream()
}
resources.add(resource)
} while (zipEntry != null)
if (resources.size() > 0) return EpubReader().readEpub(resources)
input ?: return null
FileUtils.writeInputStream(file, input)
}
return EpubReader().readEpubLazy(ZipFile(file),"utf-8")
// val inZip = ZipInputStream(input)
// var zipEntry: ZipEntry?
// val resources = Resources()
// do {
// zipEntry = inZip.nextEntry
// if ((zipEntry == null) || zipEntry.isDirectory || zipEntry == ZipEntry("<error>")) continue
// val resource = ResourceUtil.createResource(zipEntry, inZip)
// if (resource.mediaType == MediaTypes.XHTML) resource.inputEncoding = "UTF-8"
// if (zipEntry.name.endsWith(".opf")) {
// /*掌上书苑有很多自制书OPF的nameSpace格式不标准,强制修复成正确的格式*/
// val newS = String(resource.data).replace(
// "\\smlns=\"http://www.idpf.org/2007/opf\"".toRegex(),
// " xmlns=\"http://www.idpf.org/2007/opf\""
// )
// resource.data = newS.toByteArray()
// }
// resources.add(resource)
// } while (zipEntry != null)
// if (resources.size() > 0) return EpubReader().readEpub(resources)
} catch (e: Exception) {
e.printStackTrace()
}

@ -54,7 +54,9 @@ object ImageProvider {
ChapterProvider.visibleWidth,
ChapterProvider.visibleHeight
)
setCache(chapterIndex, src, bitmap)
if (bitmap != null) {
setCache(chapterIndex, src, bitmap)
}
bitmap
} catch (e: Exception) {
null

@ -9,6 +9,7 @@ import android.renderscript.RenderScript
import android.renderscript.ScriptIntrinsicBlur
import android.view.View
import splitties.init.appCtx
import java.io.FileInputStream
import java.io.IOException
import kotlin.math.*
@ -25,11 +26,12 @@ object BitmapUtils {
* @param height 想要显示的图片的高度
* @return
*/
fun decodeBitmap(path: String, width: Int, height: Int): Bitmap {
fun decodeBitmap(path: String, width: Int, height: Int): Bitmap? {
val op = BitmapFactory.Options()
var ips=FileInputStream(path)
// inJustDecodeBounds如果设置为true,仅仅返回图片实际的宽和高,宽和高是赋值给opts.outWidth,opts.outHeight;
op.inJustDecodeBounds = true
BitmapFactory.decodeFile(path, op) //获取尺寸信息
BitmapFactory.decodeStream(ips,null,op)//获取尺寸信息
//获取比例大小
val wRatio = ceil((op.outWidth / width).toDouble()).toInt()
val hRatio = ceil((op.outHeight / height).toDouble()).toInt()
@ -41,22 +43,24 @@ object BitmapUtils {
op.inSampleSize = hRatio
}
}
ips=FileInputStream(path)
op.inJustDecodeBounds = false
return BitmapFactory.decodeFile(path, op)
return BitmapFactory.decodeStream(ips,null,op)
}
/** 从path中获取Bitmap图片
* @param path 图片路径
* @return
*/
fun decodeBitmap(path: String): Bitmap {
fun decodeBitmap(path: String): Bitmap? {
val opts = BitmapFactory.Options()
var ips=FileInputStream(path)
opts.inJustDecodeBounds = true
BitmapFactory.decodeFile(path, opts)
BitmapFactory.decodeStream(ips,null,opts)
opts.inSampleSize = computeSampleSize(opts, -1, 128 * 128)
opts.inJustDecodeBounds = false
return BitmapFactory.decodeFile(path, opts)
ips=FileInputStream(path)
return BitmapFactory.decodeStream(ips,null,opts)
}
/**

@ -517,6 +517,44 @@ object FileUtils {
closeSilently(fos)
}
}
/**
* 保存文件内容
*/
fun writeInputStream(filepath: String, data: InputStream): Boolean {
val file = File(filepath)
return writeInputStream(file,data)
}
/**
* 保存文件内容
*/
fun writeInputStream(file: File, data: InputStream): Boolean {
var fos: FileOutputStream? = null
return try {
if (!file.exists()) {
file.parentFile?.mkdirs()
file.createNewFile()
}
val buffer=ByteArray(1024*4)
fos = FileOutputStream(file)
while (true) {
val len = data.read(buffer, 0, buffer.size)
if (len == -1) {
break
} else {
fos.write(buffer, 0, len)
}
}
data.close()
fos.flush()
true
} catch (e: IOException) {
false
} finally {
closeSilently(fos)
}
}
/**
* 追加文本内容

Loading…
Cancel
Save