0Pricing
Android Academy · 课时

缓存与性能

了解内存缓存和磁盘缓存基础

缓存与性能 是 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 反馈 — 无需本地设置。

此课程中的所有课时

  1. 使用 Coil 加载图片
  2. 占位符与错误状态
  3. 缓存与性能
  4. 播放音频与视频
← 返回 Android Academy