如何用Coil/Glide预加载图片至缓存并通过UID从缓存获取
Coil/Glide 实现通过UID获取预加载头像方案
问题背景
我们从服务端获取包含Base64图片数据和用户UID的AvatarPreload数据类,需要将图片预加载到内存/磁盘缓存后,给视图层提供仅含UID的轻量AvatarForView数据类,实现仅通过UID就能快速获取预加载好的图片。
Coil 解决方案
Coil的核心是通过自定义DataFetcher统一缓存Key(即UID),让预加载和后续加载逻辑共用同一缓存标识。
1. 自定义处理AvatarPreload的DataFetcher
该Fetcher负责将Base64解码为图片,并指定缓存Key为UID:
class AvatarPreloadFetcher(private val data: AvatarPreload) : DataFetcher<Drawable> { override suspend fun fetch(): FetchResult<Drawable> { // 解码Base64为字节数组 val imageBytes = android.util.Base64.decode(data.base64, android.util.Base64.DEFAULT) // 转为Drawable val drawable = BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.size)?.let { BitmapDrawable(Resources.getSystem(), it) } ?: error("Base64图片解码失败") return FetchResult.SourceResult( drawable = drawable, source = Source.MEMORY, memoryCacheKey = data.uid.toString(), diskCacheKey = data.uid.toString() ) } override fun dispose() {} override val key: String get() = data.uid.toString() }
2. 自定义处理UID(Long类型)的DataFetcher
该Fetcher负责通过UID查找内存/磁盘缓存:
class AvatarUidFetcher(private val uid: Long, private val options: ImageRequest.Options) : DataFetcher<Drawable> { override suspend fun fetch(): FetchResult<Drawable> { val cacheKey = uid.toString() // 优先从内存缓存读取 options.memoryCache?.get(cacheKey)?.let { cachedDrawable -> return FetchResult.SourceResult( drawable = cachedDrawable.drawable, source = Source.MEMORY, memoryCacheKey = cacheKey ) } // 内存缓存无数据则读取磁盘缓存 options.diskCache?.openSnapshot(cacheKey)?.use { snapshot -> val bytes = snapshot.data.readBytes() val drawable = BitmapFactory.decodeByteArray(bytes, 0, bytes.size)?.let { BitmapDrawable(Resources.getSystem(), it) } ?: error("缓存图片读取失败") return FetchResult.SourceResult( drawable = drawable, source = Source.DISK, memoryCacheKey = cacheKey, diskCacheKey = cacheKey ) } error("未找到UID为$uid的缓存图片") } override fun dispose() {} override val key: String get() = uid.toString() }
3. 注册Fetcher到ImageLoader
将上述两个Fetcher注册到Coil的ImageLoader,让Coil能识别并处理对应数据类型:
val imageLoader = ImageLoader.Builder(context) .components { // 处理AvatarPreload类型 add(AvatarPreload::class.java) { data, options, _ -> AvatarPreloadFetcher(data) } // 处理Long类型的UID add(Long::class.java) { uid, options, _ -> AvatarUidFetcher(uid, options) } } .build()
4. 预加载与视图层加载
// 预加载:传入AvatarPreload,自动存入缓存 val preloadRequest = ImageRequest.Builder(context) .data(avatarPreload) .build() imageLoader.execute(preloadRequest) // 视图层加载:直接传入UID即可获取缓存图片 val loadRequest = ImageRequest.Builder(context) .data(avatarForView.uid) .into(imageView)
Glide 解决方案
Glide通过自定义ModelLoader实现数据类型到图片资源的映射,同时统一缓存Key为UID。
1. 自定义AvatarPreloadModelLoader(预加载用)
负责解析AvatarPreload的Base64数据,并以UID作为缓存Key:
class AvatarPreloadModelLoader : ModelLoader<AvatarPreload, InputStream> { override fun handles(model: AvatarPreload): Boolean = true override fun buildLoadData( model: AvatarPreload, width: Int, height: Int, options: Options ): LoadData<InputStream>? { val cacheKey = model.uid.toString() return LoadData(ObjectKey(cacheKey), AvatarPreloadDataFetcher(model)) } class Factory : ModelLoaderFactory<AvatarPreload, InputStream> { override fun build(multiFactory: MultiModelLoaderFactory): ModelLoader<AvatarPreload, InputStream> { return AvatarPreloadModelLoader() } override fun teardown() {} } class AvatarPreloadDataFetcher(private val model: AvatarPreload) : DataFetcher<InputStream> { override fun loadData(priority: Priority, callback: DataCallback<in InputStream>) { try { val bytes = android.util.Base64.decode(model.base64, android.util.Base64.DEFAULT) callback.onDataReady(ByteArrayInputStream(bytes)) } catch (e: Exception) { callback.onLoadFailed(e) } } override fun cleanup() {} override fun cancel() {} override fun getDataClass(): Class<InputStream> = InputStream::class.java override fun getDataSource(): DataSource = DataSource.LOCAL } }
2. 自定义AvatarUidModelLoader(视图层加载用)
负责通过UID查找内存/磁盘缓存:
class AvatarUidModelLoader : ModelLoader<Long, InputStream> { override fun handles(model: Long): Boolean = true override fun buildLoadData( model: Long, width: Int, height: Int, options: Options ): LoadData<InputStream>? { val cacheKey = model.toString() return LoadData(ObjectKey(cacheKey), AvatarUidDataFetcher(model, options)) } class Factory : ModelLoaderFactory<Long, InputStream> { override fun build(multiFactory: MultiModelLoaderFactory): ModelLoader<Long, InputStream> { return AvatarUidModelLoader() } override fun teardown() {} } class AvatarUidDataFetcher(private val uid: Long, private val options: Options) : DataFetcher<InputStream> { override fun loadData(priority: Priority, callback: DataCallback<in InputStream>) { val context = options.context val cacheKey = ObjectKey(uid.toString()) // 读取内存缓存 Glide.get(context).memoryCache.get(cacheKey)?.let { resource -> val bitmap = resource.get() val bos = ByteArrayOutputStream() bitmap.compress(Bitmap.CompressFormat.PNG, 100, bos) callback.onDataReady(ByteArrayInputStream(bos.toByteArray())) return } // 读取磁盘缓存 Glide.get(context).diskCache.open(uid.toString())?.let { snapshot -> callback.onDataReady(snapshot.openInputStream()) return } callback.onLoadFailed(IllegalStateException("未找到UID为$uid的缓存图片")) } override fun cleanup() {} override fun cancel() {} override fun getDataClass(): Class<InputStream> = InputStream::class.java override fun getDataSource(): DataSource = DataSource.DISK_CACHE } }
3. 注册ModelLoader到Glide
通过自定义GlideModule注册上述ModelLoader:
@GlideModule class AvatarGlideModule : AppGlideModule() { override fun registerComponents(context: Context, glide: Glide, registry: Registry) { registry.append(AvatarPreload::class.java, InputStream::class.java, AvatarPreloadModelLoader.Factory()) registry.append(Long::class.java, InputStream::class.java, AvatarUidModelLoader.Factory()) } }
4. 预加载与视图层加载
// 预加载:传入AvatarPreload,自动存入缓存 Glide.with(context) .load(avatarPreload) .diskCacheStrategy(DiskCacheStrategy.ALL) .preload() // 视图层加载:传入UID即可获取缓存图片 Glide.with(context) .load(avatarForView.uid) .into(imageView)
内容的提问来源于stack exchange,提问作者Steven shih
相关产品推荐
相关产品推荐

