Coil图片缓存机制

以Coil 2.5.0版本为例

一、硬盘缓存

硬盘缓存为文件缓存,具体实现在coil的base中

1.1 DiskCache接口

首先来看DiskCache接口:

vbnet 复制代码
size:当前缓存所占的大小(字节)
maxSize:最大的缓存大小(字节)
directory:缓存文件存放的位置
fileSystem:缓存文件的文件系统(OKIO)

DiskCache只是一个LRU的硬盘缓存的标准,除去上面的配置还包括根据key获取目标文件,以及根据key对目标文件的一系列操作

Builder(下面会用到):

kotlin 复制代码
class Builder {

    private var directory: Path? = null
    private var fileSystem = FileSystem.SYSTEM
    private var maxSizePercent = 0.02 // 2%
    private var minimumMaxSizeBytes = 10L * 1024 * 1024 // 10MB
    private var maximumMaxSizeBytes = 250L * 1024 * 1024 // 250MB
    private var maxSizeBytes = 0L
    private var cleanupDispatcher = Dispatchers.IO

    /**
* Set the [directory] where the cache stores its data.
*
* IMPORTANT: It is an error to have two [DiskCache] instances active in the same
* directory at the same time as this can corrupt the disk cache.
*/
fun directory(directory: File) = directory(directory.toOkioPath())

    /**
* Set the [directory] where the cache stores its data.
*
* IMPORTANT: It is an error to have two [DiskCache] instances active in the same
* directory at the same time as this can corrupt the disk cache.
*/
fun directory(directory: Path) = apply {
this.directory = directory
    }

 /**
* Set the [fileSystem] where the cache stores its data, usually [FileSystem.SYSTEM].
*/
fun fileSystem(fileSystem: FileSystem) = apply {
this.fileSystem = fileSystem
    }

 /**
* Set the maximum size of the disk cache as a percentage of the device's free disk space.
*/
fun maxSizePercent(@FloatRange(from = 0.0, to = 1.0) percent: Double) = apply {
 require(percent in 0.0..1.0) { "size must be in the range [0.0, 1.0]." }
this.maxSizeBytes = 0
        this.maxSizePercent = percent
    }

 /**
* Set the minimum size of the disk cache in bytes.
* This is ignored if [maxSizeBytes] is set.
*/
fun minimumMaxSizeBytes(size: Long) = apply {
 require(size > 0) { "size must be > 0." }
this.minimumMaxSizeBytes = size
    }

 /**
* Set the maximum size of the disk cache in bytes.
* This is ignored if [maxSizeBytes] is set.
*/
fun maximumMaxSizeBytes(size: Long) = apply {
 require(size > 0) { "size must be > 0." }
this.maximumMaxSizeBytes = size
    }

 /**
* Set the maximum size of the disk cache in bytes.
*/
fun maxSizeBytes(size: Long) = apply {
 require(size > 0) { "size must be > 0." }
this.maxSizePercent = 0.0
        this.maxSizeBytes = size
    }

 /**
* Set the [CoroutineDispatcher] that cache size trim operations will be executed on.
*/
fun cleanupDispatcher(dispatcher: CoroutineDispatcher) = apply {
this.cleanupDispatcher = dispatcher
    }

 /**
* Create a new [DiskCache] instance.
*/
fun build(): DiskCache {
        val directory = checkNotNull(directory) { "directory == null" }
val maxSize = if (maxSizePercent > 0) {
            try {
                val stats = StatFs(directory.toFile().apply { mkdir() } .absolutePath)
                val size = maxSizePercent * stats.blockCountLong * stats.blockSizeLong
size.toLong().coerceIn(minimumMaxSizeBytes, maximumMaxSizeBytes)
            } catch (_: Exception) {
                minimumMaxSizeBytes
            }
        } else {
            maxSizeBytes
        }
        return RealDiskCache(
            maxSize = maxSize,
            directory = directory,
            fileSystem = fileSystem,
            cleanupDispatcher = cleanupDispatcher
        )
    }
}

1.2 DiskCache实现(代理实现以及实际实现)

kotlin 复制代码
internal class RealDiskCache(
    override val maxSize: Long,
    override val directory: Path,
    override val fileSystem: FileSystem,
    cleanupDispatcher: CoroutineDispatcher
) : DiskCache {
kotlin 复制代码
private fun String.hash() = encodeUtf8().sha256().hex()

private class RealSnapshot(private val snapshot: DiskLruCache.Snapshot) : Snapshot {

    override val metadata get() = snapshot.file(ENTRY_METADATA)
    override val data get() = snapshot.file(ENTRY_DATA)

    override fun close() = snapshot.close()
    override fun closeAndOpenEditor() = snapshot.closeAndEdit()?.let(::RealEditor)
    @Suppress("OVERRIDE_DEPRECATION")
    override fun closeAndEdit() = closeAndOpenEditor()
}

private class RealEditor(private val editor: DiskLruCache.Editor) : Editor {

    override val metadata get() = editor.file(ENTRY_METADATA)
    override val data get() = editor.file(ENTRY_DATA)

    override fun commit() = editor.commit()
    override fun commitAndOpenSnapshot() = editor.commitAndGet()?.let(::RealSnapshot)
    @Suppress("OVERRIDE_DEPRECATION")
    override fun commitAndGet() = commitAndOpenSnapshot()
    override fun abort() = editor.abort()
}

companion object {
    private const val ENTRY_METADATA = 0
    private const val ENTRY_DATA = 1
}

实现DiskCache的类只有一个:RealDiskCache,不过经过查看这个类只是一个代理类(提供文件缓存命名),实际实现DiskCache接口功能方法的则是:DiskLruCache

在看DiskLruCache的代码之前,先看一下Jaco根据此创建的文件目录以及写入的文件,有助于看下面代码的理解。

1.3 DiskLruCache实现

ini 复制代码
private val cache = DiskLruCache(
    fileSystem = fileSystem,//文件系统
    directory = directory,//目录
    cleanupDispatcher = cleanupDispatcher,//处理缓存的写成分发器
    maxSize = maxSize,//缓存最大的空间
    appVersion = 1,//标记缓存版本
    valueCount = 2,//缓存同一份文件的个数
)

//注:上方Builder实现

journal文件:

日志的前五行构成了它的标题。它们是常数串"libcore.io.DiskLruCache",磁盘缓存版本,应用程序版本,值计数和空白行。

文件中后续的每一行都是缓存项状态的记录。每一行包含以空格分隔的值:状态、键和可选的特定于状态的值。

DIRTY行跟踪正在创建或更新的条目。每一个成功的DIRTY操作之后应该是CLEAN或REMOVE操作。

没有匹配的脏行CLEAN或REMOVE表示可能需要删除临时文件。

CLEAN行跟踪已成功发布并可被读取的缓存条目。一个发布行后面跟着每个值的长度。

READ行跟踪LRU的访问。

REMOVE lines跟踪已删除的条目。

当缓存操作发生时,将追加日志文件。杂志有时可能是通过去掉多余的行来压缩。期间将使用一个名为"journal.tmp"的临时文件压实;如果该文件在打开缓存时存在,则应该删除该文件。

less 复制代码
companion object {
    @VisibleForTesting internal const val JOURNAL_FILE = "journal"
    @VisibleForTesting internal const val JOURNAL_FILE_TMP = "journal.tmp"
    @VisibleForTesting internal const val JOURNAL_FILE_BACKUP = "journal.bkp"
    @VisibleForTesting internal const val MAGIC = "libcore.io.DiskLruCache"
    @VisibleForTesting internal const val VERSION = "1"
    private const val CLEAN = "CLEAN"
    private const val DIRTY = "DIRTY"
    private const val REMOVE = "REMOVE"
    private const val READ = "READ"
    private val LEGAL_KEY_PATTERN = "[a-z0-9_-]{1,120}".toRegex()
}

1.4 FaultHidingSink

journal文件写入工具

以上是如何将网络图片写入本地缓存,以及如何利用LRU进行对网络图片缓存的增删改查操作,DiskLruCache可追溯到:JakeWhartongithub.com/JakeWharton...

二、内存缓存

下面就是重头戏了:

2.1 WeakMemoryCache,EmptyWeakMemoryCache,RealWeakMemoryCache

bitmap的虚引用缓存,当bitmap从强引用内存缓存被回收时会加入到此缓存池

这里主要看set方法调用即可

kotlin 复制代码
internal class RealStrongMemoryCache(
    maxSize: Int,
    private val weakMemoryCache: WeakMemoryCache
) : StrongMemoryCache {

    private val cache = object : LruCache<Key, InternalValue>(maxSize) {
        override fun sizeOf(key: Key, value: InternalValue) = value.size
        override fun entryRemoved(
            evicted: Boolean,
            key: Key,
            oldValue: InternalValue,
            newValue: InternalValue?
        ) = weakMemoryCache.set(key, oldValue.bitmap, oldValue.extras, oldValue.size)
    }
    
    override fun set(key: Key, bitmap: Bitmap, extras: Map<String, Any>) {
    val size = bitmap.allocationByteCountCompat
if (size <= maxSize) {
        cache.put(key, InternalValue(bitmap, extras, size))
    } else {
        // If the bitmap is too big for the cache, don't attempt to store it as doing
        // so will cause the cache to be cleared. Instead, evict an existing element
        // with the same key if it exists and add the bitmap to the weak memory cache.
        cache.remove(key)
        weakMemoryCache.set(key, bitmap, extras, size)
        }
    }
    
    //省略部分代码
    
}

2.2 StrongMemoryCache,EmptyStrongMemoryCache,StrongMemoryCache

强引用bitmap缓存池,同Weak配合,上方有详细代码

2.3 MemoryCache,RealMemoryCache

将弱引用和强引用组合在一起的缓存,也就是内存缓存,这里直接分析代码即可:

kotlin 复制代码
internal class RealMemoryCache(//两个入参,强弱引用缓存
    private val strongMemoryCache: StrongMemoryCache,
    private val weakMemoryCache: WeakMemoryCache
) : MemoryCache {

    override val size get() = strongMemoryCache.size

    override val maxSize get() = strongMemoryCache.maxSize
    //根据key获取bitmap,key的范围限制为强弱两个缓存池
    override val keys get() = strongMemoryCache.keys + weakMemoryCache.keys
    //优先获取强引用缓存,查找失败则获取弱引用缓存,由上方可知,因为从强引用缓存中删除的bitmap会由弱引用缓存继续缓存起来
    override fun get(key: Key): MemoryCache.Value? {
        return strongMemoryCache.get(key) ?: weakMemoryCache.get(key)
    }

    override fun set(key: Key, value: MemoryCache.Value) {
        // Ensure that stored keys and values are immutable.
        strongMemoryCache.set(
            key = key.copy(extras = key.extras.toImmutableMap()),
            bitmap = value.bitmap,
            extras = value.extras.toImmutableMap()
        )
        // weakMemoryCache.set() is called by strongMemoryCache when
        // a value is evicted from the strong reference cache.
    }
    //根据key同时删除强弱引用缓存
    override fun remove(key: Key): Boolean {
        // Do not short circuit. There is a regression test for this.
        val removedStrong = strongMemoryCache.remove(key)
        val removedWeak = weakMemoryCache.remove(key)
        return removedStrong || removedWeak
    }

    override fun clear() {
        strongMemoryCache.clearMemory()
        weakMemoryCache.clearMemory()
    }

    override fun trimMemory(level: Int) {
        strongMemoryCache.trimMemory(level)
        weakMemoryCache.trimMemory(level)
    }
}

2.4 MemoryCacheService

2.4.1 MemoryCache.Key获取(这里以File为例):

kotlin 复制代码
internal class FileKeyer(private val addLastModifiedToFileCacheKey: Boolean) : Keyer<File> {

    override fun key(data: File, options: Options): String {
        return if (addLastModifiedToFileCacheKey) {
            "${data.path}:${data.lastModified()}"
        } else {
            data.path
}
    }
}
kotlin 复制代码
class ComponentRegistry private constructor(
    val interceptors: List<Interceptor>,
    val mappers: List<Pair<Mapper<out Any, out Any>, Class<out Any>>>,
    val keyers: List<Pair<Keyer<out Any>, Class<out Any>>>,
    val fetcherFactories: List<Pair<Fetcher.Factory<out Any>, Class<out Any>>>,
    val decoderFactories: List<Decoder.Factory>
) {
    /**
* Convert [data] to a string key using the registered [keyers].
* 省略部分代码
* @return The cache key, or 'null' if [data] should not be cached.
*/
fun key(data: Any, options: Options): String? {
        keyers.forEachIndices { (keyer, type) ->
if (type.isAssignableFrom(data::class.java)) {
                (keyer as Keyer<Any>).key(data, options)?.let { return it }
}
        }
return null
    }
 }
kotlin 复制代码
fun newCacheKey(
    request: ImageRequest,
    mappedData: Any,
    options: Options,
    eventListener: EventListener
): MemoryCache.Key? {
    // Fast path: an explicit memory cache key has been set.
    request.memoryCacheKey?.let { return it }

// Slow path: create a new memory cache key.
    eventListener.keyStart(request, mappedData)
    val base = imageLoader.components.key(mappedData, options)
    eventListener.keyEnd(request, base)
    if (base == null) return null

    // Optimize for the typical case where there are no transformations or parameters.
    val transformations = request.transformations
    val parameterKeys = request.parameters.memoryCacheKeys()
    if (transformations.isEmpty() && parameterKeys.isEmpty()) {
        return MemoryCache.Key(base)
    }

    // Else, create a memory cache key with extras.
    val extras = parameterKeys.toMutableMap()
    if (transformations.isNotEmpty()) {
        request.transformations.forEachIndexedIndices { index, transformation ->
extras[EXTRA_TRANSFORMATION_INDEX + index] = transformation.cacheKey
        }
extras[EXTRA_TRANSFORMATION_SIZE] = options.size.toString()
    }
    return MemoryCache.Key(base, extras)
}

2.4.2 MemoryCache.Value创建以及获取

kotlin 复制代码
fun getCacheValue(
    request: ImageRequest,
    cacheKey: MemoryCache.Key,
    size: Size,
    scale: Scale,
): MemoryCache.Value? {
    if (!request.memoryCachePolicy.readEnabled) return null
    val cacheValue = imageLoader.memoryCache?.get(cacheKey)
    return cacheValue?.takeIf { isCacheValueValid(request, cacheKey, it, size, scale) }
}

三、缓存策略的注入

3.1 RealImageLoader

scss 复制代码
internal class RealImageLoader(
    val context: Context,
    override val defaults: DefaultRequestOptions,
    val memoryCacheLazy: Lazy<MemoryCache?>,
    val diskCacheLazy: Lazy<DiskCache?>,
    val callFactoryLazy: Lazy<Call.Factory>,
    val eventListenerFactory: EventListener.Factory,
    val componentRegistry: ComponentRegistry,
    val options: ImageLoaderOptions,
    val logger: Logger?,
) : ImageLoader {

    private val interceptors = components.interceptors + EngineInterceptor(this, requestService, logger)
    
    @MainThread
private suspend fun executeMain(initialRequest: ImageRequest, type: Int): ImageResult {
    // Wrap the request to manage its lifecycle.
    val requestDelegate = requestService.requestDelegate(initialRequest, coroutineContext.job)
        .apply { assertActive() }

// Apply this image loader's defaults to this request.
    val request = initialRequest.newBuilder().defaults(defaults).build()

    // Create a new event listener.
    val eventListener = eventListenerFactory.create(request)

    try {
        // Fail before starting if data is null.
        if (request.data === NullRequestData) {
            throw NullRequestDataException()
        }

        // Set up the request's lifecycle observers.
        requestDelegate.start()

        // Enqueued requests suspend until the lifecycle is started.
        if (type == REQUEST_TYPE_ENQUEUE) {
            request.lifecycle.awaitStarted()
        }

        // Set the placeholder on the target.
        val placeholderBitmap = memoryCache?.get(request.placeholderMemoryCacheKey)?.bitmap
        val placeholder = placeholderBitmap?.toDrawable(request.context) ?: request.placeholder
        request.target?.onStart(placeholder)
        eventListener.onStart(request)
        request.listener?.onStart(request)

        // Resolve the size.
        eventListener.resolveSizeStart(request)
        val size = request.sizeResolver.size()
        eventListener.resolveSizeEnd(request, size)

        // Execute the interceptor chain.
        val result = withContext(request.interceptorDispatcher) {
RealInterceptorChain(
                initialRequest = request,
                interceptors = interceptors,
                index = 0,
                request = request,
                size = size,
                eventListener = eventListener,
                isPlaceholderCached = placeholderBitmap != null
            ).proceed(request)
        }

// Set the result on the target.
        when (result) {
            is SuccessResult -> onSuccess(result, request.target, eventListener)
            is ErrorResult -> onError(result, request.target, eventListener)
        }
        return result
    } catch (throwable: Throwable) {
        if (throwable is CancellationException) {
            onCancel(request, eventListener)
            throw throwable
        } else {
            // Create the default error result if there's an uncaught exception.
            val result = requestService.errorResult(request, throwable)
            onError(result, request.target, eventListener)
            return result
        }
    } finally {
        requestDelegate.complete()
    }
  }
}

3.2 EngineInterceptor

kotlin 复制代码
internal class EngineInterceptor(
    private val imageLoader: ImageLoader,
    private val requestService: RequestService,
    private val logger: Logger?,
) : Interceptor {

    private val memoryCacheService = MemoryCacheService(imageLoader, requestService, logger)

    override suspend fun intercept(chain: Interceptor.Chain): ImageResult {
        try {
            val request = chain.request
            val data = request.data
            val size = chain.size
            val eventListener = chain.eventListener
val options = requestService.options(request, size)
            val scale = options.scale

            // Perform any data mapping.
            eventListener.mapStart(request, data)
            val mappedData = imageLoader.components.map(data, options)
            eventListener.mapEnd(request, mappedData)

            // Check the memory cache.
            val cacheKey = memoryCacheService.newCacheKey(request, mappedData, options, eventListener)
            val cacheValue = cacheKey?.let { memoryCacheService.getCacheValue(request, it, size, scale) }

// Fast path: return the value from the memory cache.
            if (cacheValue != null) {
                return memoryCacheService.newResult(chain, request, cacheKey, cacheValue)
            }

            // Slow path: fetch, decode, transform, and cache the image.
            return withContext(request.fetcherDispatcher) {
// Fetch and decode the image.
                val result = execute(request, mappedData, options, eventListener)

                // Write the result to the memory cache.
                val isCached = memoryCacheService.setCacheValue(cacheKey, request, result)

                // Return the result.
                SuccessResult(
                    drawable = result.drawable,
                    request = request,
                    dataSource = result.dataSource,
                    memoryCacheKey = cacheKey.takeIf { isCached } ,
                    diskCacheKey = result.diskCacheKey,
                    isSampled = result.isSampled,
                    isPlaceholderCached = chain.isPlaceholderCached,
                )
            }
} catch (throwable: Throwable) {
            if (throwable is CancellationException) {
                throw throwable
            } else {
                return requestService.errorResult(chain.request, throwable)
            }
        }
    }
}

四、创建实例

4.1 ImageLoader

ini 复制代码
interface ImageLoader {
    //Create a new ImageLoader instance.
    fun build(): ImageLoader {
    return RealImageLoader(
        context = applicationContext,
        defaults = defaults,
        memoryCacheLazy = memoryCache ?: lazy { MemoryCache.Builder(applicationContext).build() } ,
        diskCacheLazy = diskCache ?: lazy { SingletonDiskCache.get(applicationContext) } ,
        callFactoryLazy = callFactory ?: lazy { OkHttpClient() } ,
        eventListenerFactory = eventListenerFactory ?: EventListener.Factory.NONE,
        componentRegistry = componentRegistry ?: ComponentRegistry(),
        options = options,
        logger = logger
    )
    }
}

4.2 ImageLoaders

kotlin 复制代码
 /**
* Create a new [ImageLoader] without configuration.
*/
@JvmName("create")
fun ImageLoader(context: Context): ImageLoader {
    return ImageLoader.Builder(context).build()
}

/**
* Execute the [request] and block the current thread until it completes.
*
* @see ImageLoader.execute
*/
@WorkerThread
fun ImageLoader.executeBlocking(request: ImageRequest): ImageResult {
    return runBlocking { execute(request) }
}

五、调用

5.1调用链

ini 复制代码
AsyncImage(model = imageUrl1, contentDescription = null)

5.2 关于网络图片和本地图片缓存策略的问题

为什么网络图片会被缓存而本地的不会?

着重看下面这几个文件,优先看HttpUriFetcher这个

5.2.1 HttpUriFetcher

kotlin 复制代码
internal class HttpUriFetcher(
    private val url: String,
    private val options: Options,
    private val callFactory: Lazy<Call.Factory>,
    private val diskCache: Lazy<DiskCache?>,
    private val respectCacheHeaders: Boolean
) : Fetcher {

    override suspend fun fetch(): FetchResult {
        var snapshot = readFromDiskCache()
        try {
            // Fast path: fetch the image from the disk cache without performing a network request.
            val cacheStrategy: CacheStrategy
            if (snapshot != null) {
                // Always return cached images with empty metadata as they were likely added manually.
                if (fileSystem.metadata(snapshot.metadata).size == 0L) {
                    return SourceResult(
                        source = snapshot.toImageSource(),
                        mimeType = getMimeType(url, null),
                        dataSource = DataSource.DISK
)
                }

                // Return the candidate from the cache if it is eligible.
                if (respectCacheHeaders) {
                    cacheStrategy = CacheStrategy.Factory(newRequest(), snapshot.toCacheResponse()).compute()
                    if (cacheStrategy.networkRequest == null && cacheStrategy.cacheResponse != null) {
                        return SourceResult(
                            source = snapshot.toImageSource(),
                            mimeType = getMimeType(url, cacheStrategy.cacheResponse.contentType),
                            dataSource = DataSource.DISK
)
                    }
                } else {
                    // Skip checking the cache headers if the option is disabled.
                    return SourceResult(
                        source = snapshot.toImageSource(),
                        mimeType = getMimeType(url, snapshot.toCacheResponse()?.contentType),
                        dataSource = DataSource.DISK
)
                }
            } else {
                cacheStrategy = CacheStrategy.Factory(newRequest(), null).compute()
            }

            // Slow path: fetch the image from the network.
            var response = executeNetworkRequest(cacheStrategy.networkRequest!!)
            var responseBody = response.requireBody()
            try {
                // Write the response to the disk cache then open a new snapshot.
                snapshot = writeToDiskCache(
                    snapshot = snapshot,
                    request = cacheStrategy.networkRequest,
                    response = response,
                    cacheResponse = cacheStrategy.cacheResponse
                )
                if (snapshot != null) {
                    return SourceResult(
                        source = snapshot.toImageSource(),
                        mimeType = getMimeType(url, snapshot.toCacheResponse()?.contentType),
                        dataSource = DataSource.NETWORK
)
                }

                // If we failed to read a new snapshot then read the response body if it's not empty.
                if (responseBody.contentLength() > 0) {
                    return SourceResult(
                        source = responseBody.toImageSource(),
                        mimeType = getMimeType(url, responseBody.contentType()),
                        dataSource = response.toDataSource()
                    )
                } else {
                    // If the response body is empty, execute a new network request without the
                    // cache headers.
                    response.closeQuietly()
                    response = executeNetworkRequest(newRequest())
                    responseBody = response.requireBody()

                    return SourceResult(
                        source = responseBody.toImageSource(),
                        mimeType = getMimeType(url, responseBody.contentType()),
                        dataSource = response.toDataSource()
                    )
                }
            } catch (e: Exception) {
                response.closeQuietly()
                throw e
            }
        } catch (e: Exception) {
            snapshot?.closeQuietly()
            throw e
        }
    }

    private fun readFromDiskCache(): DiskCache.Snapshot? {
        return if (options.diskCachePolicy.readEnabled) {
            diskCache.value?.openSnapshot(diskCacheKey)
        } else {
            null
        }
    }

    private fun writeToDiskCache(
        snapshot: DiskCache.Snapshot?,
        request: Request,
        response: Response,
        cacheResponse: CacheResponse?
    ): DiskCache.Snapshot? {
        // Short circuit if we're not allowed to cache this response.
        if (!isCacheable(request, response)) {
            snapshot?.closeQuietly()
            return null
        }

        // Open a new editor.
        val editor = if (snapshot != null) {
            snapshot.closeAndOpenEditor()
        } else {
            diskCache.value?.openEditor(diskCacheKey)
        }

        // Return `null` if we're unable to write to this entry.
        if (editor == null) return null

        try {
            // Write the response to the disk cache.
            if (response.code == HTTP_NOT_MODIFIED && cacheResponse != null) {
                // Only update the metadata.
                val combinedResponse = response.newBuilder()
                    .headers(combineHeaders(cacheResponse.responseHeaders, response.headers))
                    .build()
                fileSystem.write(editor.metadata) {
CacheResponse(combinedResponse).writeTo(this)
                }
} else {
                // Update the metadata and the image data.
                fileSystem.write(editor.metadata) {
CacheResponse(response).writeTo(this)
                }
fileSystem.write(editor.data) {
response.body!!.source().readAll(this)
                }
}
            return editor.commitAndOpenSnapshot()
        } catch (e: Exception) {
            editor.abortQuietly()
            throw e
        } finally {
            response.closeQuietly()
        }
    }

    private fun newRequest(): Request {
        val request = Request.Builder()
            .url(url)
            .headers(options.headers)

        // Attach all custom tags to this request.
        @Suppress("UNCHECKED_CAST")
        options.tags.asMap().forEach { request.tag(it.key as Class<Any>, it.value) }

val diskRead = options.diskCachePolicy.readEnabled
        val networkRead = options.networkCachePolicy.readEnabled
        when {
            !networkRead && diskRead -> {
                request.cacheControl(CacheControl.FORCE_CACHE)
            }
            networkRead && !diskRead -> if (options.diskCachePolicy.writeEnabled) {
                request.cacheControl(CacheControl.FORCE_NETWORK)
            } else {
                request.cacheControl(CACHE_CONTROL_FORCE_NETWORK_NO_CACHE)
            }
            !networkRead && !diskRead -> {
                // This causes the request to fail with a 504 Unsatisfiable Request.
                request.cacheControl(CACHE_CONTROL_NO_NETWORK_NO_CACHE)
            }
        }

        return request.build()
    }

    private suspend fun executeNetworkRequest(request: Request): Response {
        val response = if (isMainThread()) {
            if (options.networkCachePolicy.readEnabled) {
                // Prevent executing requests on the main thread that could block due to a
                // networking operation.
                throw NetworkOnMainThreadException()
            } else {
                // Work around: https://github.com/Kotlin/kotlinx.coroutines/issues/2448
                callFactory.value.newCall(request).execute()
            }
        } else {
            // Suspend and enqueue the request on one of OkHttp's dispatcher threads.
            callFactory.value.newCall(request).await()
        }
        if (!response.isSuccessful && response.code != HTTP_NOT_MODIFIED) {
            response.body?.closeQuietly()
            throw HttpException(response)
        }
        return response
    }

    /**
* Parse the response's `content-type` header.
*
* "text/plain" is often used as a default/fallback MIME type.
* Attempt to guess a better MIME type from the file extension.
*/
@VisibleForTesting
    internal fun getMimeType(url: String, contentType: MediaType?): String? {
        val rawContentType = contentType?.toString()
        if (rawContentType == null || rawContentType.startsWith(MIME_TYPE_TEXT_PLAIN)) {
            MimeTypeMap.getSingleton().getMimeTypeFromUrl(url)?.let { return it }
}
        return rawContentType?.substringBefore(';')
    }

    private fun isCacheable(request: Request, response: Response): Boolean {
        return options.diskCachePolicy.writeEnabled &&
            (!respectCacheHeaders || CacheStrategy.isCacheable(request, response))
    }

    private fun DiskCache.Snapshot.toCacheResponse(): CacheResponse? {
        try {
            return fileSystem.read(metadata) {
CacheResponse(this)
            }
} catch (_: IOException) {
            // If we can't parse the metadata, ignore this entry.
            return null
        }
    }

    private fun DiskCache.Snapshot.toImageSource(): ImageSource {
        return ImageSource(data, fileSystem, diskCacheKey, this)
    }

    private fun ResponseBody.toImageSource(): ImageSource {
        return ImageSource(source(), options.context)
    }

    private fun Response.toDataSource(): DataSource {
        return if (networkResponse != null) DataSource.NETWORK else DataSource.DISK
}

    private val diskCacheKey get() = options.diskCacheKey ?: url

    private val fileSystem get() = diskCache.value!!.fileSystem

    class Factory(
        private val callFactory: Lazy<Call.Factory>,
        private val diskCache: Lazy<DiskCache?>,
        private val respectCacheHeaders: Boolean
    ) : Fetcher.Factory<Uri> {

        override fun create(data: Uri, options: Options, imageLoader: ImageLoader): Fetcher? {
            if (!isApplicable(data)) return null
            return HttpUriFetcher(data.toString(), options, callFactory, diskCache, respectCacheHeaders)
        }

        private fun isApplicable(data: Uri): Boolean {
            return data.scheme == "http" || data.scheme == "https"
        }
    }

    companion object {
        private const val MIME_TYPE_TEXT_PLAIN = "text/plain"
        private val CACHE_CONTROL_FORCE_NETWORK_NO_CACHE =
            CacheControl.Builder().noCache().noStore().build()
        private val CACHE_CONTROL_NO_NETWORK_NO_CACHE =
            CacheControl.Builder().noCache().onlyIfCached().build()
    }
}

5.2.2同FileFetcher,AssetUriFetcher,ContentUriFetcher作对比

这些本地数据都是通过简单的stream和bitmap进行加载,并未注入缓存,并且这些source被认为是DISK所以不会被缓存。

相关推荐
GISer_Jing3 小时前
前端知识详解——HTML/CSS/Javascript/ES5+/Typescript篇/算法篇
前端·javascript·面试
写不来代码的草莓熊3 小时前
vue前端面试题——记录一次面试当中遇到的题(4)
前端·javascript·vue.js·面试
道可到3 小时前
阿里面试原题 java面试直接过06 | 集合底层——HashMap、ConcurrentHashMap、CopyOnWriteArrayList
java·后端·面试
吃饺子不吃馅3 小时前
小明问:要不要加入创业公司?
前端·面试·github
这可不简单3 小时前
前端面试题:请求层缓存与并发控制的完整设计(含原理拆解)
前端·javascript·面试
小奋斗4 小时前
浏览器原理之详解渲染进程
前端·面试
uhakadotcom4 小时前
deno在2025年新出了哪些api可供使用?
前端·面试·github
uhakadotcom4 小时前
2025年honojs提供了哪些新的基础能力与API可以使用?
前端·javascript·面试
初听于你5 小时前
MySQL数据库面试高频问题及解析
数据库·sql·mysql·oracle·面试