携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第12天,点击查看活动详情
前言
前文再续,书接上一回。之前讲到拦截器链中唯一的拦截器,它的名字就是——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。
Name InputType OutputType HttpUrlMapper HttpUrl String StringMapper String Uri FileUriMapper Uri File ResourceUriMapper Uri(资源路径形式) Uri(资源ID形式) ResourceIntMapper Int Uri ByteArrayMapper ByteArray ByteBuffer 遍历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:
| Name | handleType | OutputType |
|---|---|---|
| HttpUriFetcher | Uri | SourceResult |
| FileFetcher | File | SourceResult |
| AssetUriFetcher | Uri | SourceResult |
| ContentUriFetcher | Uri | SourceResult |
| ResourceUriFetcher | Uri | SourceResult or DrawableResult |
| DrawableFetcher | Drawable | DrawableResult |
| BitmapFetcher | Bitmap | DrawableResult |
| ByteBufferFetcher | ByteBuffer | SourceResult |
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只有两种:
| Name | Description |
|---|---|
| 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相关原理等等。