Coil源码解析(二)

442 阅读4分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第12天,点击查看活动详情

Coil源码解析(一)

前言

前文再续,书接上一回。之前讲到拦截器链中唯一的拦截器,它的名字就是——EngineInterceptor。作为核心拦截器,它也是最后一个拦截器。

EngineInterceptor.intercept()

先上代码:

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
​
        //①
        eventListener.mapStart(request, data)
        val mappedData = imageLoader.components.map(data, options)
        eventListener.mapEnd(request, mappedData)
​
        // ②
        val cacheKey = memoryCacheService.newCacheKey(request, mappedData, options, eventListener)
        val cacheValue = cacheKey?.let { memoryCacheService.getCacheValue(request, it, size, scale) }
​
        // ③
        if (cacheValue != null) {
            return memoryCacheService.newResult(chain, request, cacheKey, cacheValue)
        }
​
        // ④
        return withContext(request.fetcherDispatcher) {
            val result = execute(request, mappedData, options, eventListener)
​
            val isCached = memoryCacheService.setCacheValue(cacheKey, request, 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)
        }
    }
}

整体大概分为四步:

  • ①处代码。映射data的类型。如前一篇所说,data支持多种类型,但其中并不是所有类型都能直接处理,部分类型需要进行一定的转换。为此,Coil设计了Mapper(映射表)机制。下表列出Coil内置的六个Mapper。

    NameInputTypeOutputType
    HttpUrlMapperHttpUrlString
    StringMapperStringUri
    FileUriMapperUriFile
    ResourceUriMapperUri(资源路径形式)Uri(资源ID形式)
    ResourceIntMapperIntUri
    ByteArrayMapperByteArrayByteBuffer

    遍历Mapper,如果data类型符合Mapper的InputType,则执行该Mapper的map,对data进行类型转换,输出OutputType。

  • ②处代码。根据Request和经过类型转换的data等数据创建cacheKey(内存缓存的Key),并且根据cacheKey查找cacheValue(该值持有缓存下来的Bitmap)。

  • ③处代码。如果查找到缓存数据,则使用该缓存数据包装成SuccessResult返回。

  • ④处代码。如果没有查找到缓存数据,则执行该Request,并将数据写入缓存,最后将数据包装成SuccessResult返回。这里的fetcherDispatcher未指定的情况下,依然是主线程。

execute()

重点自然就是④处代码中的execute方法。execute的代码就不贴了,重点不在这方法本身。

graph LR
Invoke --> 配置FetcherFactory 
subgraph execute
配置FetcherFactory --> 配置DecoderFactory --> fetch --> |SourceResult| decode --> transform
fetch --> |DrawableResult| transform
end
transform --> Return

fetch()

private suspend fun fetch(
    components: ComponentRegistry,
    request: ImageRequest,
    mappedData: Any,
    options: Options,
    eventListener: EventListener
): FetchResult {
    val fetchResult: FetchResult
    var searchIndex = 0
    while (true) {
        //根据searchIndex以及mappedData类型获取Fetcher
        val pair = components.newFetcher(mappedData, options, imageLoader, searchIndex)
        checkNotNull(pair) { "Unable to create a fetcher that supports: $mappedData" }
        val fetcher = pair.first
        //searchIndex加一,预备给下一个循环使用
        searchIndex = pair.second + 1
​
        eventListener.fetchStart(request, fetcher, options)
        //获取数据
        val result = fetcher.fetch()
        try {
            eventListener.fetchEnd(request, fetcher, options, result)
        } catch (throwable: Throwable) {
            (result as? SourceResult)?.source?.closeQuietly()
            throw throwable
        }
​
        //获取到数据则跳出循环返回结果
        if (result != null) {
            fetchResult = result
            break
        }
    }
    return fetchResult
}

关键有二:newFetcher()以及fecher.fetch(),都与Fetcher有关。

Fetcher为接口,负责将data(注意这里的data是我们传入的Uri、File等)转换ImageSource(远端数据如:网络、磁盘等)或者Drawable(直接数据:Bitmap等)。

下表归纳了Coil内置的Fetcher:

NamehandleTypeOutputType
HttpUriFetcherUriSourceResult
FileFetcherFileSourceResult
AssetUriFetcherUriSourceResult
ContentUriFetcherUriSourceResult
ResourceUriFetcherUriSourceResult or DrawableResult
DrawableFetcherDrawableDrawableResult
BitmapFetcherBitmapDrawableResult
ByteBufferFetcherByteBufferSourceResult

newFetcher()会根据传入的mappedData的类型与上表中的handleType匹配,匹配到则返回对应的Fetcher。

从上面的表格与execute的流程图可以看出,不同类型的data会得到SourceResult或者DrawableResult。

  • SourceResult:数据需要先进行解码才能显示。
  • DrawableResult:数据基本可以直接进行显示。

如果是SourceResult,则进入解码流程。

decode()

private suspend fun decode(
    fetchResult: SourceResult,
    components: ComponentRegistry,
    request: ImageRequest,
    mappedData: Any,
    options: Options,
    eventListener: EventListener
): ExecuteResult {
    val decodeResult: DecodeResult
    var searchIndex = 0
    while (true) {
        val pair = components.newDecoder(fetchResult, options, imageLoader, searchIndex)
        checkNotNull(pair) { "Unable to create a decoder that supports: $mappedData" }
        val decoder = pair.first
        searchIndex = pair.second + 1
​
        eventListener.decodeStart(request, decoder, options)
        val result = decoder.decode()
        eventListener.decodeEnd(request, decoder, options, result)
​
        if (result != null) {
            decodeResult = result
            break
        }
    }
​
    return ExecuteResult(
        drawable = decodeResult.drawable,
        isSampled = decodeResult.isSampled,
        dataSource = fetchResult.dataSource,
        diskCacheKey = (fetchResult.source as? FileImageSource)?.diskCacheKey
    )
}

可以说,跟fetch()的代码基本一样。直接解析Decoder。

内置的Decoder只有一个:BitmapFactoryDecoder。里面解码的过程使用了OKio,笔者不才,看不懂……

transform()

internal suspend fun transform(
    result: ExecuteResult,
    request: ImageRequest,
    options: Options,
    eventListener: EventListener
): ExecuteResult {
    //①
    val transformations = request.transformations
    if (transformations.isEmpty()) return result
​
    //②
    if (result.drawable !is BitmapDrawable && !request.allowConversionToBitmap) {
        logger?.log(TAG, Log.INFO) {
            val type = result.drawable::class.java.canonicalName
            "allowConversionToBitmap=false, skipping transformations for type $type."
        }
        return result
    }
​
    //③
    return withContext(request.transformationDispatcher) {
        val input = convertDrawableToBitmap(result.drawable, options, transformations)
        eventListener.transformStart(request, input)
        val output = transformations.foldIndices(input) { bitmap, transformation ->
            transformation.transform(bitmap, options.size).also { ensureActive() }
        }
        eventListener.transformEnd(request, output)
        result.copy(drawable = output.toDrawable(request.context))
    }
}
  • ①处代码。获取需要执行的Transformation列表。列表为空则说明无需进行任何变换,直接返回。
  • ②处代码。如果禁用了Bitmap转换则跳过,直接返回。
  • ③处代码。将SourceResult中的Drawable转成Bitmap,然后遍历Transformation列表逐一进行变换。变换完成后将变换后的Bitmap转回Drawable并返回。注意这里的transformationDispatcher实际上是IO线程,也就是说这里面执行的线程会被切换到IO线程。同样的,fetch和decode的线程也被指定为IO线程。这就解释了为什么一开始执行Request的时候可以指定主线程执行。

内置的Transformation只有两种:

NameDescription
CircleCropTransformation将原图切成圆形
RoundedCornersTransformation原图应用圆角(四角都可设置)

RealImageLoader.onSuccess()

RealImageLoader从RealInterceptorChain中拿到图片的Drawable后,会调用onSuccess方法(当然是获取成功的情况下)。

private fun onSuccess(
    result: SuccessResult,
    target: Target?,
    eventListener: EventListener
) {
    val request = result.request
    val dataSource = result.dataSource
​
    //处理动画后通知Target获取成功并传递Drawable
    transition(result, target, eventListener) { target?.onSuccess(result.drawable) }
    eventListener.onSuccess(request, result)
    request.listener?.onSuccess(request, result)
}

Target.onSuccess()

由于本例是将图片显示在ImageView中,所以具体使用的Target实现类为ImageViewTarget。但ImageViewTarget中并没有实现onSuccess。于是追溯到它的父类GenericViewTarget。

abstract class GenericViewTarget<T : View> : ViewTarget<T>, TransitionTarget, DefaultLifecycleObserver {
    //……
    //省略部分代码
    //……
    
    override fun onSuccess(result: Drawable) = updateDrawable(result)
​
    protected fun updateDrawable(drawable: Drawable?) {
        (this.drawable as? Animatable)?.stop()
        this.drawable = drawable
        updateAnimation()
    }
​
    protected fun updateAnimation() {
        val animatable = drawable as? Animatable ?: return
        if (isStarted) animatable.start() else animatable.stop()
    }
}
​

代码很简单,将加载完成的drawable设入View中,并开始动画(如果有)。

结言

Coil的主要流程解析到此就结束了,总体来说比Glide要简单不少,没有Glide那么多的弯弯绕绕。后面也会考虑继续这个系列,讲讲进阶用法以及其实现原理、缓存机制实现原理、OKio相关原理等等。