- Coil 2.5.0
- Kotlin 1.9.10
问题现象
mBinding.bannerHomeFgHome.load(url)
在加载图片时,如果多次调用以上代码可能会出现闪烁/抖动的情况,比如 Tab 频繁切换、接口回调频繁等。
问题原因
Coil 开始加载时会强制使用 placeholder 做占位图。
即使我们没有设置,它也会尝试通过一些途径获取一个,如果结果为 null,就会导致 ImageView 出现空白。这时候如果没有固定高度,则整个视图都会发生抖动。
RealImageLoader L169-172
// 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)
GenericViewTarget L44-48
protected fun updateDrawable(drawable: Drawable?) {
(this.drawable as? Animatable)?.stop()
this.drawable = drawable
updateAnimation()
}
解决方法
将当前 drawable 设置为 placeholder
mBinding.bannerHomeFgHome.load(url){
placeholder(mBinding.bannerHomeFgHome.drawable)
}
placeholderMemoryCacheKey 的尝试
虽然 coil 首先会通过request.placeholderMemoryCacheKey 尝试从缓存中获取 bitmap,官方也有相关文章描述如何使用,但我尝试是实现不了的,至少在当前版本(2.5.0)是这样。
因为官方做法是通过ImageRequest获取memoryCacheKey,类似这样:
mBinding.bannerHomeFgHome.result?.request?.memoryCacheKey
但是这里的 memoryCacheKey 并不是最终 Coil 使用的 key,而是我们通过 load 方法传入的,因为我们仅仅填入了 data 参数,所以其他值当然一直为 null。
当然我们没填不代表 Coil 就不启用内存缓存和磁盘缓存,我们可以在 Coil 加载图片的源码 EngineInterceptor#intercept 中看到,cacheKey 实际上是通过 MemoryCacheService.newCacheKey 方法返回的,内部判断了如果memoryCacheKey为 null 的时候,会根据 data、Transform 等参数生成一个唯一 key。
如果我们能找到最终的这个 memoryCacheKey也可以使用它来实现 placeholder。
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)
}