From d30379251d2b895bd1e7d7c4470d1bb54868dfca Mon Sep 17 00:00:00 2001 From: tarsin Date: Mon, 13 Feb 2023 18:21:57 +0800 Subject: [PATCH] Refactor Image source provider Skia use SKMemoryStream to read file mapping data SKMemoryStream always use memcpy to emulate posix read stream behaviour, cannot take advantages of file mapping reading * https://github.com/google/skia/blob/1f193df9b393d50da39570dab77a0bb5d28ec8ef/src/core/SkStream.cpp#L335 --- .../main/java/com/hippo/UriArchiveAccessor.kt | 11 +-- .../ehviewer/gallery/ArchivePageLoader.java | 10 +-- .../com/hippo/ehviewer/spider/SpiderDen.kt | 81 ++++++------------- .../hippo/ehviewer/spider/SpiderQueen.java | 2 +- app/src/main/java/com/hippo/image/Image.kt | 28 +++---- 5 files changed, 47 insertions(+), 85 deletions(-) diff --git a/app/src/main/java/com/hippo/UriArchiveAccessor.kt b/app/src/main/java/com/hippo/UriArchiveAccessor.kt index 804ab69d4..8f9565ba9 100644 --- a/app/src/main/java/com/hippo/UriArchiveAccessor.kt +++ b/app/src/main/java/com/hippo/UriArchiveAccessor.kt @@ -18,6 +18,7 @@ package com.hippo import android.content.Context +import android.graphics.ImageDecoder import android.net.Uri import com.hippo.image.Image import java.nio.ByteBuffer @@ -40,15 +41,15 @@ class UriArchiveAccessor(ctx: Context, uri: Uri) { pfd.close() } - fun getImageSource(index: Int): Image.ByteBufferSource? { + fun getImageSource(index: Int): Image.CloseableSource? { val buffer = extractToByteBuffer(index) buffer ?: return null check(buffer.isDirect) Image.rewriteGifSource(buffer) - return object : Image.ByteBufferSource { - override fun getByteBuffer(): ByteBuffer { - return buffer - } + val source = ImageDecoder.createSource(buffer) + return object : Image.CloseableSource { + override val source: ImageDecoder.Source + get() = source override fun close() { releaseByteBuffer(buffer) diff --git a/app/src/main/java/com/hippo/ehviewer/gallery/ArchivePageLoader.java b/app/src/main/java/com/hippo/ehviewer/gallery/ArchivePageLoader.java index abf4a499c..f2e28019f 100644 --- a/app/src/main/java/com/hippo/ehviewer/gallery/ArchivePageLoader.java +++ b/app/src/main/java/com/hippo/ehviewer/gallery/ArchivePageLoader.java @@ -54,7 +54,7 @@ public class ArchivePageLoader extends PageLoader2 { public static PVLock pv = new PVLock(0); private final UriArchiveAccessor archiveAccessor; private final Stack requests = new Stack<>(); - private final LinkedHashMap streams = new LinkedHashMap<>(); + private final LinkedHashMap streams = new LinkedHashMap<>(); private final Thread[] decodeThread = new Thread[]{ new Thread(new DecodeTask()), new Thread(new DecodeTask()), @@ -303,7 +303,7 @@ public class ArchivePageLoader extends PageLoader2 { } } - Image.ByteBufferSource src = archiveAccessor.getImageSource(index); + Image.CloseableSource src = archiveAccessor.getImageSource(index); synchronized (streams) { streams.put(index, src); @@ -318,7 +318,7 @@ public class ArchivePageLoader extends PageLoader2 { public void run() { while (!Thread.currentThread().isInterrupted()) { int index; - Image.ByteBufferSource src; + Image.CloseableSource src; synchronized (streams) { if (streams.isEmpty()) { try { @@ -329,8 +329,8 @@ public class ArchivePageLoader extends PageLoader2 { continue; } - Iterator> iterator = streams.entrySet().iterator(); - Map.Entry entry = iterator.next(); + Iterator> iterator = streams.entrySet().iterator(); + Map.Entry entry = iterator.next(); iterator.remove(); index = entry.getKey(); src = entry.getValue(); diff --git a/app/src/main/java/com/hippo/ehviewer/spider/SpiderDen.kt b/app/src/main/java/com/hippo/ehviewer/spider/SpiderDen.kt index 18b66be12..ee7418839 100644 --- a/app/src/main/java/com/hippo/ehviewer/spider/SpiderDen.kt +++ b/app/src/main/java/com/hippo/ehviewer/spider/SpiderDen.kt @@ -15,12 +15,11 @@ */ package com.hippo.ehviewer.spider +import android.graphics.ImageDecoder import coil.decode.DecodeUtils import coil.decode.FrameDelayRewritingSource import coil.decode.isGif import coil.disk.DiskCache -import com.hippo.Native.mapFd -import com.hippo.Native.unmapDirectByteBuffer import com.hippo.ehviewer.EhApplication import com.hippo.ehviewer.EhApplication.Companion.application import com.hippo.ehviewer.EhDB @@ -30,7 +29,7 @@ import com.hippo.ehviewer.client.EhRequestBuilder import com.hippo.ehviewer.client.EhUtils.getSuitableTitle import com.hippo.ehviewer.client.data.GalleryInfo import com.hippo.ehviewer.gallery.PageLoader2 -import com.hippo.image.Image.ByteBufferSource +import com.hippo.image.Image.CloseableSource import com.hippo.unifile.RawFile import com.hippo.unifile.UniFile import com.hippo.yorozuya.FileUtils @@ -43,13 +42,12 @@ import java.io.File import java.io.FileInputStream import java.io.FileOutputStream import java.io.IOException -import java.io.RandomAccessFile -import java.nio.ByteBuffer import java.nio.channels.FileChannel import java.util.Locale import kotlin.io.path.readText private val client = EhApplication.okHttpClient +private val contentResolver = application.contentResolver class SpiderDen(private val mGalleryInfo: GalleryInfo) { private val mGid: Long = mGalleryInfo.gid @@ -180,7 +178,9 @@ class SpiderDen(private val mGalleryInfo: GalleryInfo) { suspend fun doSave(chan: FileChannel): Long { var receivedSize: Long = 0 body.source().use { responseSource -> - val source = if (DecodeUtils.isGif(responseSource)) FrameDelayRewritingSource(responseSource).buffer() else responseSource + val source = if (DecodeUtils.isGif(responseSource)) FrameDelayRewritingSource( + responseSource + ).buffer() else responseSource source.use { while (true) { currentCoroutineContext().ensureActive() @@ -278,29 +278,19 @@ class SpiderDen(private val mGalleryInfo: GalleryInfo) { ?.name.let { FileUtils.getExtensionFromFilename(it) } } - fun getImageSource(index: Int): ByteBufferSource? { + fun getImageSource(index: Int): CloseableSource? { if (mMode == SpiderQueen.MODE_READ) { val key = EhCacheKeyFactory.getImageKey(mGid, index) val snapshot: DiskCache.Snapshot? = sCache[key] if (snapshot != null) { - try { - val file = RandomAccessFile(snapshot.data.toFile(), "rw") - val chan = file.channel - val map = chan.map(FileChannel.MapMode.PRIVATE, 0, file.length()) - return object : ByteBufferSource { - override fun getByteBuffer(): ByteBuffer { - return map - } + val source = ImageDecoder.createSource(snapshot.data.toFile()) + return object : CloseableSource { + override val source: ImageDecoder.Source + get() = source - @Throws(Exception::class) - override fun close() { - chan.close() - file.close() - snapshot.close() - } + override fun close() { + snapshot.close() } - } catch (e: Throwable) { - e.printStackTrace() } } } @@ -312,46 +302,21 @@ class SpiderDen(private val mGalleryInfo: GalleryInfo) { } } if (file is RawFile) { - try { - val file2 = RandomAccessFile(file.mFile, "rw") - val chan = file2.channel - val map = chan.map(FileChannel.MapMode.PRIVATE, 0, file2.length()) - return object : ByteBufferSource { - override fun getByteBuffer(): ByteBuffer { - return map - } + val source = ImageDecoder.createSource(file.mFile) + return object : CloseableSource { + override val source: ImageDecoder.Source + get() = source - @Throws(Exception::class) - override fun close() { - chan.close() - file2.close() - } - } - } catch (e: Throwable) { - e.printStackTrace() + override fun close() {} } } if (file != null) { - try { - val pfd = application.contentResolver.openFileDescriptor(file.uri, "rw") - val map = mapFd(pfd!!.fd, pfd.statSize) - if (map != null) { - return object : ByteBufferSource { - override fun getByteBuffer(): ByteBuffer { - return map - } + val source = ImageDecoder.createSource(contentResolver, file.uri) + return object : CloseableSource { + override val source: ImageDecoder.Source + get() = source - @Throws(Exception::class) - override fun close() { - unmapDirectByteBuffer(map) - pfd.close() - } - } - } else { - pfd.close() - } - } catch (e: Throwable) { - e.printStackTrace() + override fun close() {} } } return null diff --git a/app/src/main/java/com/hippo/ehviewer/spider/SpiderQueen.java b/app/src/main/java/com/hippo/ehviewer/spider/SpiderQueen.java index 2d6878f50..c099a1c29 100644 --- a/app/src/main/java/com/hippo/ehviewer/spider/SpiderQueen.java +++ b/app/src/main/java/com/hippo/ehviewer/spider/SpiderQueen.java @@ -855,7 +855,7 @@ public final class SpiderQueen implements Runnable { continue; } - Image.ByteBufferSource src = mSpiderDen.getImageSource(index); + Image.CloseableSource src = mSpiderDen.getImageSource(index); if (src == null) { resetDecodeIndex(); // Can't find the file, it might be removed from cache, diff --git a/app/src/main/java/com/hippo/image/Image.kt b/app/src/main/java/com/hippo/image/Image.kt index 93e208455..8d367236b 100644 --- a/app/src/main/java/com/hippo/image/Image.kt +++ b/app/src/main/java/com/hippo/image/Image.kt @@ -21,7 +21,7 @@ import android.graphics.ColorSpace import android.graphics.ImageDecoder import android.graphics.ImageDecoder.ImageInfo import android.graphics.ImageDecoder.Source -import android.graphics.drawable.Animatable +import android.graphics.drawable.AnimatedImageDrawable import android.graphics.drawable.BitmapDrawable import android.graphics.drawable.Drawable import android.os.Build @@ -30,9 +30,9 @@ import com.hippo.ehviewer.R import java.nio.ByteBuffer import kotlin.math.min -class Image private constructor(source: Source, private var src: ByteBufferSource?) { +class Image private constructor(private val src: CloseableSource) { var mObtainedDrawable: Drawable? = - ImageDecoder.decodeDrawable(source) { decoder: ImageDecoder, info: ImageInfo, _: Source -> + ImageDecoder.decodeDrawable(src.source) { decoder: ImageDecoder, info: ImageInfo, _: Source -> if (Build.VERSION.SDK_INT == Build.VERSION_CODES.P) { // Allocating hardware bitmap may cause a crash on framework versions prior to Android Q decoder.allocator = ImageDecoder.ALLOCATOR_SOFTWARE @@ -43,23 +43,21 @@ class Image private constructor(source: Source, private var src: ByteBufferSourc ) }.also { (it as? BitmapDrawable)?.run { - src?.close() - src = null + src.close() } } private set val size: Int - get() = mObtainedDrawable!!.run { intrinsicHeight * intrinsicWidth * 4 * if (this is Animatable) 4 else 1 } + get() = mObtainedDrawable!!.run { intrinsicHeight * intrinsicWidth * 4 * if (this is AnimatedImageDrawable) 4 else 1 } @Synchronized fun recycle() { - (mObtainedDrawable as? Animatable)?.stop() + (mObtainedDrawable as? AnimatedImageDrawable)?.stop() (mObtainedDrawable as? BitmapDrawable)?.bitmap?.recycle() mObtainedDrawable?.callback = null mObtainedDrawable = null - src?.close() - src = null + (mObtainedDrawable as? AnimatedImageDrawable)?.let { src.close() } } companion object { @@ -91,10 +89,9 @@ class Image private constructor(source: Source, private var src: ByteBufferSourc ) @JvmStatic - fun decode(src: ByteBufferSource): Image? { - val directBuffer = src.getByteBuffer() + fun decode(src: CloseableSource): Image? { return runCatching { - Image(ImageDecoder.createSource(directBuffer), src) + Image(src) }.onFailure { src.close() it.printStackTrace() @@ -105,8 +102,7 @@ class Image private constructor(source: Source, private var src: ByteBufferSourc external fun rewriteGifSource(buffer: ByteBuffer) } - interface ByteBufferSource : AutoCloseable { - // A read-write direct bytebuffer, it may in native heap or file mapping - fun getByteBuffer(): ByteBuffer + interface CloseableSource : AutoCloseable { + val source: Source } -} \ No newline at end of file +}