缓存与性能
了解内存缓存和磁盘缓存基础
缓存与性能 是 CoddyKit 上的免费 Android Academy 课时。 这是第 3 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Android Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Android Academy 课程共包含 4 节课。
缓存为何重要
下载和解码图片的成本很高:会占用带宽、电量和 CPU。如果每次列表滚动时都重新获取同一张图片,应用会显得缓慢,还会消耗大量流量。
缓存 会存储已解码的图片和已下载的字节数据,使下一次请求几乎可以瞬间完成。Coil 默认提供两种缓存:内存缓存(速度快,存储已解码的位图)和磁盘缓存(持久化存储原始字节数据)。
本课将解释它们的工作方式以及如何调整它们。
两层缓存:内存与磁盘
Coil 会按以下顺序检查缓存:
- 内存缓存:在 RAM 中保存最近使用的已解码位图。命中后可以瞬间显示,无需解码。
- 磁盘缓存:在设备上存储最初下载的字节数据。命中后无需访问网络,但仍需要解码。
- 网络:速度最慢的路径,只有在两种缓存都未命中时才会使用。
内存速度快,但容量小,并且应用终止时会被清除;磁盘容量更大,应用重启后仍然保留。
配置 ImageLoader
缓存位于 ImageLoader 中,它是每个请求背后的引擎。通常,您会创建一个自定义加载器并重复使用它。在这里,我们会将内存缓存设置为可用 RAM 的一定百分比,并将磁盘缓存设置为固定的字节容量。
使用单例加载器可以避免重复创建缓存和浪费内存。
import android.content.Context
import coil3.ImageLoader
import coil3.disk.DiskCache
import coil3.memory.MemoryCache
import okio.Path.Companion.toOkioPath
fun newImageLoader(context: Context): ImageLoader =
ImageLoader.Builder(context)
.memoryCache {
MemoryCache.Builder()
.maxSizePercent(context, 0.25) // 25% of app RAM
.build()
}
.diskCache {
DiskCache.Builder()
.directory(context.cacheDir.resolve("image_cache").toOkioPath())
.maxSizeBytes(50L * 1024 * 1024) // 50 MB
.build()
}
.build()提供单例加载器
Coil 3 允许您在 Application 类上实现 SingletonImageLoader.Factory,从而设置一个应用级别的统一加载器。之后,每个 AsyncImage 都会自动使用它,无需每次都传入加载器。
import android.app.Application
import coil3.ImageLoader
import coil3.PlatformContext
import coil3.SingletonImageLoader
class MyApp : Application(), SingletonImageLoader.Factory {
override fun newImageLoader(context: PlatformContext): ImageLoader =
ImageLoader.Builder(context)
.crossfade(true)
.build()
}控制缓存策略
每个请求都可以通过 memoryCachePolicy 和 diskCachePolicy 选择启用或停用缓存。策略包括 ENABLED、READ_ONLY、WRITE_ONLY 和 DISABLED。
例如,对于不希望保留在 RAM 中的一次性超大图片,可以停用内存缓存;对于敏感内容,则可以停用磁盘缓存。
import android.content.Context
import coil3.request.CachePolicy
import coil3.request.ImageRequest
fun noMemoryCacheRequest(context: Context, url: String) =
ImageRequest.Builder(context)
.data(url)
.memoryCachePolicy(CachePolicy.DISABLED)
.diskCachePolicy(CachePolicy.ENABLED)
.build()调整大小:不要解码超大位图
最严重的性能错误之一,是将一张 4000x3000 的照片解码为 100dp 的缩略图。这会浪费内存和时间。默认情况下,Coil 会根据可组合项测量出的边界调整位图大小,因此较小的 AsyncImage 会解码出较小的位图。
如果您知道具体尺寸,也可以在请求上使用 size() 强制指定目标尺寸。
import android.content.Context
import coil3.request.ImageRequest
import coil3.size.Size
fun thumbnailRequest(context: Context, url: String) =
ImageRequest.Builder(context)
.data(url)
.size(Size(200, 200)) // decode at most 200x200
.build()稳定的缓存键
Coil 会根据请求数据生成缓存键。如果 URL 包含会变化的查询参数(例如签名令牌),相同的图片就会得到不同的键,从而永远无法命中缓存。
请根据稳定的部分(例如图片 ID)设置明确的 memoryCacheKey 和 diskCacheKey,这样缓存才能正常工作。
import android.content.Context
import coil3.request.ImageRequest
fun stableKeyRequest(context: Context, id: String, signedUrl: String) =
ImageRequest.Builder(context)
.data(signedUrl)
.memoryCacheKey("image_$id")
.diskCacheKey("image_$id")
.build()预加载图片
为了让体验更加流畅,您可以在图片显示之前预热缓存,例如预取信息流的下一页。请使用加载器的 enqueue,并传入不带目标的请求。
当用户滚动到这张图片时,它已经在缓存中,可以立即显示。
import android.content.Context
import coil3.ImageLoader
import coil3.request.ImageRequest
fun preload(context: Context, loader: ImageLoader, urls: List<String>) {
urls.forEach { url ->
val request = ImageRequest.Builder(context)
.data(url)
.build()
loader.enqueue(request)
}
}清除缓存
有时您需要释放空间或强制刷新内容,例如用户更新头像后。您可以清除内存缓存、磁盘缓存,或移除单个键。
清除所有内容开销较大;如果可以,请优先移除特定的键。
import coil3.ImageLoader
import coil3.memory.MemoryCache
fun refreshAvatar(loader: ImageLoader, key: String) {
// Remove one entry so the next load refetches
loader.memoryCache?.remove(MemoryCache.Key(key))
loader.diskCache?.remove(key)
}
fun clearAll(loader: ImageLoader) {
loader.memoryCache?.clear()
// Disk clearing is heavier; do it sparingly
}交叉淡化与缓存命中
对于网络加载,交叉淡化效果很不错;但当图片直接来自内存缓存、原本可以立即显示时,这种效果可能会让人觉得迟缓。Coil 对此进行了智能处理:默认情况下,内存缓存命中时会跳过交叉淡化动画,只对真正加载完成的图片播放动画。
因此,首次加载时会有平滑的淡入效果,滚动到已缓存的图片时则会立即显示,无需额外处理。
import android.content.Context
import coil3.request.ImageRequest
import coil3.request.crossfade
fun feedRequest(context: Context, url: String) =
ImageRequest.Builder(context)
.data(url)
// Crossfade plays on real loads; memory-cache hits show instantly
.crossfade(true)
.build()列表性能检查清单
在 LazyColumn 或网格中显示大量图片时,请遵循以下规则,以实现流畅滚动:
- 为每张图片指定固定尺寸,让 Coil 解码较小的位图。
- 重复使用一个单例
ImageLoader,以便共享缓存。 - 使用
ContentScale.Crop,避免布局跳动。 - 确保缓存键在重新加载期间保持稳定。
- 让 Coil 自动取消屏幕外的请求,不要干预它。
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.items
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.ContentScale
import coil3.compose.AsyncImage
@Composable
fun PhotoGrid(urls: List<String>) {
LazyVerticalGrid(columns = GridCells.Fixed(3)) {
items(urls) { url ->
AsyncImage(
model = url,
contentDescription = null,
contentScale = ContentScale.Crop,
modifier = Modifier.aspectRatio(1f)
)
}
}
}快速检查
Coil 会在访问网络前检查缓存。命中哪种缓存时速度最快,因为它可以完全跳过解码?
回顾
您已经了解了 Coil 如何让图片加载保持高效:
- 两种缓存:内存缓存(已解码、速度快、容量小)和磁盘缓存(原始字节数据、持久化、容量更大)。
- 在自定义
ImageLoader上配置容量,并通过SingletonImageLoader.Factory提供一个统一的加载器。 - 使用
memoryCachePolicy和diskCachePolicy调整每个请求的行为。 - 始终按照显示尺寸进行解码;必要时设置明确的
size()。 - 使用稳定的缓存键,预加载即将显示的图片,并通过清除特定的键来刷新内容。
下一步:从图片扩展到音频和视频播放。
常见问题解答
「缓存与性能」课时是免费的吗?
是的 — 「缓存与性能」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Android Academy 课程的其余内容,请升级到 CoddyKit PRO。 Android Academy 课程共包含 4 节课。
「缓存与性能」这节课中我会学到什么?
了解内存缓存和磁盘缓存基础 你通过在浏览器中直接运行的动手代码来练习 Android Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。
学习 Android Academy 需要有经验吗?
无需任何先前经验。CoddyKit 上的 Android Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 3 节课,共 4 节。
「缓存与性能」课时需要多长时间?
大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。
我能在这节 Android Academy 课中编写并运行代码吗?
能。每节 Android Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。