Jetpack Compose中Coil图片缓存失效及API请求重复疑问
我用OkHttp缓存API请求结果并存储到ViewModel中,之后通过AsyncImage加载ViewModel里的图片链接。但使用Coil 2.6.0做图片缓存时遇到问题:有网络时图片加载正常,断网后无法从缓存读取图片,只会显示默认图片。
另外还有个疑问:不管开启或关闭网络,API请求的override fun onResponse(call: Call, response: Response)方法总会被调用(日志打印"Coil cache image success"),想知道这是不是意味着API被重复调用?ViewModel中打印的数据始终是成功返回的列表。
代码片段
1. UI Compose部分
val imageUrl = searchViewModel.listEvent.item[index].content?.image Log.d("TienDM", "imageUrl $imageUrl") // 日志显示有图片链接 val listener = object : ImageRequest.Listener { override fun onError(request: ImageRequest, result: ErrorResult) { Log.d("TienDM", "Coil cache image failed") // 断网时这条日志没触发 super.onError(request, result) } override fun onSuccess(request: ImageRequest, result: SuccessResult) { Log.d("TienDM", "Coil cache image success") super.onSuccess(request, result) } } val imageRequest = ImageRequest.Builder(context) .data(imageUrl) .listener(listener) .memoryCacheKey(imageUrl) .diskCacheKey(imageUrl) .placeholder(R.drawable.image_faztaa_app) .error(R.drawable.image_faztaa_app) .fallback(R.drawable.image_faztaa_app) .networkCachePolicy(CachePolicy.ENABLED) .diskCachePolicy(CachePolicy.ENABLED) .memoryCachePolicy(CachePolicy.ENABLED) .crossfade(true) .build() SubcomposeAsyncImage( model = imageRequest )
2. ViewModel部分
private fun loadImageEvent() { viewModelScope.launch(Dispatchers.IO) { val client = OkHttpClient().newBuilder() .cache(Cache(File(context.cacheDir, "http-cache"), 1L * 1024L * 1024L)) // 1 MiB .addNetworkInterceptor(cacheInterceptor) // 仅当服务器未启用Cache-Control头时使用 .addInterceptor(forceCacheInterceptor) .build() val request = Request.Builder() .url("/event/list") .build() client.newCall(request).enqueue(object : Callback { override fun onFailure(call: Call, e: IOException) { Log.d("TienDM", "get api failed") } override fun onResponse(call: Call, response: Response) { if (response.isSuccessful) { val stringBody = response.body?.string() Log.d("TienDM", "get api success $stringBody") stringBody?.let { try { val events = Gson().fromJson<List<ResponseEventList>>(stringBody, object : TypeToken<List<ResponseEventList>>() {}.type) _listEvent.update { events } } catch (_: JsonSyntaxException) { } catch (_: JsonSyntaxException) { } catch (_: IllegalStateException) { } } } } }) } }
3. Application配置部分
override fun newImageLoader(): ImageLoader { return ImageLoader.Builder(this) .components { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { add(ImageDecoderDecoder.Factory()) } else{ add(GifDecoder.Factory()) } } .crossfade(true) .bitmapConfig(bitmapConfig = Bitmap.Config.ARGB_8888) .memoryCachePolicy(CachePolicy.ENABLED) .memoryCache { MemoryCache.Builder(this) .maxSizePercent(0.1) .weakReferencesEnabled(true) .build() } .diskCachePolicy(CachePolicy.ENABLED) .diskCache { DiskCache.Builder() .directory(File(cacheDir,"image_cache").apply { mkdirs() }) .maxSizePercent(0.001) .build() } .logger(DebugLogger()) .build() }
问题解决
一、Coil无网络无法读取缓存图片的修复方案
调整磁盘缓存大小
你当前配置的磁盘缓存占比0.001(仅0.1%)太小,大概率导致图片缓存被系统或Coil清理。修改为合理值,比如:.diskCache { DiskCache.Builder() .directory(File(cacheDir,"image_cache").apply { mkdirs() }) .maxSize(5L * 1024 * 1024) // 固定5MB缓存 // 或者用占比:.maxSizePercent(0.05) // 占总存储5% .build() }补全缓存拦截器逻辑
你提到的cacheInterceptor和forceCacheInterceptor缺失实现,这是OkHttp缓存生效的关键。示例实现:// 检查网络状态的工具方法 private fun isNetworkAvailable(context: Context): Boolean { val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager val network = connectivityManager.activeNetwork ?: return false val networkCapabilities = connectivityManager.getNetworkCapabilities(network) ?: return false return networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) } // 强制缓存拦截器:无网络时直接用缓存 val forceCacheInterceptor = Interceptor { chain -> var request = chain.request() if (!isNetworkAvailable(context)) { request = request.newBuilder() .cacheControl(CacheControl.FORCE_CACHE) .build() } chain.proceed(request) } // 缓存控制拦截器:设置缓存有效期 val cacheInterceptor = Interceptor { chain -> val response = chain.proceed(chain.request()) val cacheControl = CacheControl.Builder() .maxAge(7, TimeUnit.DAYS) // 缓存7天 .build() response.newBuilder() .header("Cache-Control", cacheControl.toString()) .build() }优化Coil请求配置
去掉手动设置的memoryCacheKey和diskCacheKey,Coil会自动根据URL生成唯一缓存键,手动设置可能导致键冲突。同时明确无网络时的缓存策略:val imageRequest = ImageRequest.Builder(context) .data(imageUrl) .listener(listener) .placeholder(R.drawable.image_faztaa_app) .error(R.drawable.image_faztaa_app) .fallback(R.drawable.image_faztaa_app) .networkCachePolicy(if (isNetworkAvailable(context)) CachePolicy.ENABLED else CachePolicy.READ_ONLY) .diskCachePolicy(CachePolicy.ENABLED) .memoryCachePolicy(CachePolicy.ENABLED) .crossfade(true) .build()验证缓存写入状态
打开设备的文件管理器,查看/data/data/你的包名/cache/image_cache目录下是否有缓存文件;或者通过Coil的DebugLogger查看日志,确认缓存是否命中。
二、API请求onResponse始终被调用的原因
这不代表API被重复发起网络请求,是OkHttp的缓存机制导致的:
- 有网络时:OkHttp会先检查本地缓存是否有效,若有效直接返回缓存响应,不会发起网络请求;若缓存过期,才会发起网络请求并更新缓存。
- 无网络时:OkHttp直接返回本地缓存的响应,不会发起网络请求。
你可以通过以下代码判断是否是缓存响应:
override fun onResponse(call: Call, response: Response) { if (response.cacheResponse() != null) { Log.d("TienDM", "从缓存读取API响应") } else { Log.d("TienDM", "从网络读取API响应") } // 原有逻辑... }
内容的提问来源于stack exchange,提问作者Minh Tiến Đào

