From d15c5fc9d157ad5ba879c21c208febea0e77d1b0 Mon Sep 17 00:00:00 2001 From: tarsin Date: Sun, 19 Feb 2023 18:08:35 +0800 Subject: [PATCH] Implement image request merging in standalone Interceptor, Don't modify coil --- app/build.gradle.kts | 3 +- .../java/com/hippo/ehviewer/EhApplication.kt | 2 + .../hippo/ehviewer/coil/MergeInterceptor.kt | 76 +++++++++++++++++++ 3 files changed, 79 insertions(+), 2 deletions(-) create mode 100644 app/src/main/java/com/hippo/ehviewer/coil/MergeInterceptor.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 7ce106038..866b141ec 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -190,8 +190,7 @@ dependencies { implementation("dev.rikka.rikkax.preference:simplemenu-preference:1.0.3") implementation("dev.rikka.rikkax.material:material-preference:2.0.0") // https://coil-kt.github.io/coil/changelog/ - // implementation 'io.coil-kt:coil:2.2.2' - implementation("com.github.Ehviewer-Overhauled.coil:coil-compose:53302f2") + implementation("io.coil-kt:coil-compose:2.2.2") implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4") implementation("org.jetbrains.kotlinx:kotlinx-serialization-cbor:1.5.0-RC") diff --git a/app/src/main/java/com/hippo/ehviewer/EhApplication.kt b/app/src/main/java/com/hippo/ehviewer/EhApplication.kt index a6f9807c0..d62828746 100644 --- a/app/src/main/java/com/hippo/ehviewer/EhApplication.kt +++ b/app/src/main/java/com/hippo/ehviewer/EhApplication.kt @@ -35,6 +35,7 @@ import com.hippo.ehviewer.client.EhDns import com.hippo.ehviewer.client.EhSSLSocketFactory import com.hippo.ehviewer.client.EhTagDatabase import com.hippo.ehviewer.client.data.GalleryDetail +import com.hippo.ehviewer.coil.MergeInterceptor import com.hippo.ehviewer.dao.buildMainDB import com.hippo.ehviewer.download.DownloadManager import com.hippo.ehviewer.ui.CommonOperations @@ -186,6 +187,7 @@ class EhApplication : Application(), DefaultLifecycleObserver, ImageLoaderFactor override fun newImageLoader(): ImageLoader { return ImageLoader.Builder(this).apply { okHttpClient(okHttpClient) + components { add(MergeInterceptor) } allowRgb565(getSystemService()!!.isLowRamDevice) // Coil spawns a new thread for every image load by default fetcherDispatcher(Dispatchers.IO.limitedParallelism(8)) diff --git a/app/src/main/java/com/hippo/ehviewer/coil/MergeInterceptor.kt b/app/src/main/java/com/hippo/ehviewer/coil/MergeInterceptor.kt new file mode 100644 index 000000000..e7075a340 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/coil/MergeInterceptor.kt @@ -0,0 +1,76 @@ +package com.hippo.ehviewer.coil + +import coil.intercept.Interceptor +import coil.request.ImageRequest +import coil.request.ImageResult +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.launch +import kotlinx.coroutines.suspendCancellableCoroutine +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock +import kotlinx.coroutines.withContext +import kotlin.coroutines.Continuation +import kotlin.coroutines.resume + +object MergeInterceptor : Interceptor { + private val pendingContinuationMap: HashMap>> = hashMapOf() + private val pendingContinuationMapLock = Mutex() + private val notifyScope = CoroutineScope(Dispatchers.IO + SupervisorJob()) + override suspend fun intercept(chain: Interceptor.Chain): ImageResult { + val originRequest = chain.request + val originListener = originRequest.listener + val key = originRequest.data as String + var newRequest = originRequest + + pendingContinuationMapLock.lock() + val existPendingContinuations = pendingContinuationMap[key] + if (existPendingContinuations == null) { + pendingContinuationMap[key] = mutableListOf() + newRequest = ImageRequest.Builder(originRequest).listener( + { originListener?.onStart(it) }, + { + originListener?.onCancel(it) + notifyScope.launch { + pendingContinuationMapLock.withLock { + // Wake up a pending continuation to continue executing task + val successor = pendingContinuationMap[key]?.removeFirstOrNull()?.apply { resume(Unit) } + // If no successor, delete this entry from hashmap + successor ?: pendingContinuationMap.remove(key) + } + } + }, + { request, result -> + originListener?.onError(request, result) + // Wake all pending continuations since this request is to be failed + notifyScope.launch { + pendingContinuationMapLock.withLock { + pendingContinuationMap.remove(key)?.forEach { it.resume(Unit) } + } + } + }, + { request, result -> + originListener?.onSuccess(request, result) + // Wake all pending continuations shared with the same memory key since we have written it to memory cache + notifyScope.launch { + pendingContinuationMapLock.withLock { + pendingContinuationMap.remove(key)?.forEach { it.resume(Unit) } + } + } + } + ).build() + pendingContinuationMapLock.unlock() + } else { + existPendingContinuations.apply { + suspendCancellableCoroutine { continuation -> + add(continuation) + pendingContinuationMapLock.unlock() + continuation.invokeOnCancellation { remove(continuation) } + } + } + } + + return withContext(originRequest.interceptorDispatcher) { chain.proceed(newRequest) } + } +} \ No newline at end of file