You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 17:21:02