fix(mobile): cronet image cache clearing on android (#27054)

This commit is contained in:
Luis Nachtigall
2026-03-20 23:28:24 +01:00
committed by GitHub
parent 5a8fd40dc5
commit 00dae6ac38
2 changed files with 44 additions and 40 deletions

View File

@@ -23,10 +23,18 @@ import okhttp3.HttpUrl
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import org.chromium.net.CronetEngine import org.chromium.net.CronetEngine
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import java.io.ByteArrayInputStream import java.io.ByteArrayInputStream
import java.io.File import java.io.File
import java.io.IOException
import java.nio.file.FileVisitResult
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.SimpleFileVisitor
import java.nio.file.attribute.BasicFileAttributes
import java.net.Authenticator import java.net.Authenticator
import java.net.CookieHandler import java.net.CookieHandler
import java.net.PasswordAuthentication import java.net.PasswordAuthentication
@@ -277,10 +285,13 @@ object HttpClientManager {
return result return result
} }
fun rebuildCronetEngine(): CronetEngine { suspend fun rebuildCronetEngine(): Result<Long> {
val old = cronetEngine!! return runCatching {
cronetEngine?.shutdown()
val deletionResult = deleteFolderAndGetSize(cronetStoragePath.toPath())
cronetEngine = buildCronetEngine() cronetEngine = buildCronetEngine()
return old deletionResult
}
} }
val cronetStoragePath: File get() = cronetStorageDir val cronetStoragePath: File get() = cronetStorageDir
@@ -301,7 +312,7 @@ object HttpClientManager {
} }
} }
private fun buildCronetEngine(): CronetEngine { fun buildCronetEngine(): CronetEngine {
return CronetEngine.Builder(appContext) return CronetEngine.Builder(appContext)
.enableHttp2(true) .enableHttp2(true)
.enableQuic(true) .enableQuic(true)
@@ -312,6 +323,27 @@ object HttpClientManager {
.build() .build()
} }
private suspend fun deleteFolderAndGetSize(root: Path): Long = withContext(Dispatchers.IO) {
var totalSize = 0L
Files.walkFileTree(root, object : SimpleFileVisitor<Path>() {
override fun visitFile(file: Path, attrs: BasicFileAttributes): FileVisitResult {
totalSize += attrs.size()
Files.delete(file)
return FileVisitResult.CONTINUE
}
override fun postVisitDirectory(dir: Path, exc: IOException?): FileVisitResult {
if (dir != root) {
Files.delete(dir)
}
return FileVisitResult.CONTINUE
}
})
totalSize
}
private fun build(cacheDir: File): OkHttpClient { private fun build(cacheDir: File): OkHttpClient {
val connectionPool = ConnectionPool( val connectionPool = ConnectionPool(
maxIdleConnections = KEEP_ALIVE_CONNECTIONS, maxIdleConnections = KEEP_ALIVE_CONNECTIONS,

View File

@@ -21,11 +21,6 @@ import java.io.EOFException
import java.io.File import java.io.File
import java.io.IOException import java.io.IOException
import java.nio.ByteBuffer import java.nio.ByteBuffer
import java.nio.file.FileVisitResult
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.SimpleFileVisitor
import java.nio.file.attribute.BasicFileAttributes
import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.ConcurrentHashMap
private class RemoteRequest(val cancellationSignal: CancellationSignal) private class RemoteRequest(val cancellationSignal: CancellationSignal)
@@ -205,20 +200,17 @@ private class CronetImageFetcher : ImageFetcher {
private fun onDrained() { private fun onDrained() {
val onCacheCleared = synchronized(stateLock) { val onCacheCleared = synchronized(stateLock) {
val onCacheCleared = onCacheCleared val onCacheCleared = this.onCacheCleared
this.onCacheCleared = null this.onCacheCleared = null
onCacheCleared onCacheCleared
} } ?: return
if (onCacheCleared != null) {
val oldEngine = HttpClientManager.rebuildCronetEngine()
oldEngine.shutdown()
CoroutineScope(Dispatchers.IO).launch { CoroutineScope(Dispatchers.IO).launch {
val result = runCatching { deleteFolderAndGetSize(HttpClientManager.cronetStoragePath.toPath()) } val result = HttpClientManager.rebuildCronetEngine()
synchronized(stateLock) { draining = false } synchronized(stateLock) { draining = false }
onCacheCleared(result) onCacheCleared(result)
} }
} }
}
override fun clearCache(onCleared: (Result<Long>) -> Unit) { override fun clearCache(onCleared: (Result<Long>) -> Unit) {
synchronized(stateLock) { synchronized(stateLock) {
@@ -306,26 +298,6 @@ private class CronetImageFetcher : ImageFetcher {
} }
} }
suspend fun deleteFolderAndGetSize(root: Path): Long = withContext(Dispatchers.IO) {
var totalSize = 0L
Files.walkFileTree(root, object : SimpleFileVisitor<Path>() {
override fun visitFile(file: Path, attrs: BasicFileAttributes): FileVisitResult {
totalSize += attrs.size()
Files.delete(file)
return FileVisitResult.CONTINUE
}
override fun postVisitDirectory(dir: Path, exc: IOException?): FileVisitResult {
if (dir != root) {
Files.delete(dir)
}
return FileVisitResult.CONTINUE
}
})
totalSize
}
} }
private class OkHttpImageFetcher private constructor( private class OkHttpImageFetcher private constructor(