以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可追溯到:JakeWharton :github.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所以不会被缓存。