第2章 配置强缓存策略:Cache-Control与Expires头设置、服务端与客户端协同、静态资源版本化

强缓存是WebView缓存体系中最直接、性能收益最高的优化手段。当资源被强缓存命中时,WebView不会向服务端发送任何网络请求,直接从本地缓存加载资源,延迟可降至毫秒级。本章围绕Cache-Control与Expires头的配置、服务端与客户端的协同机制以及静态资源版本化展开,给出可落地的技术实现方案。

2.1 强缓存策略的核心原理

强缓存依赖HTTP响应头中的Cache-Control或Expires字段,浏览器和WebView在加载资源时首先检查本地缓存副本是否在有效期内。如果未过期,则直接使用缓存副本,整个过程零网络开销。

指令值示例作用优先级
max-age31536000指定资源有效期(秒),相对时间高
s-maxage31536000公共缓存(CDN)专用有效期最高
public—允许所有代理和客户端缓存—
private—仅允许客户端缓存,禁止代理缓存—
no-cache—强制每次使用前向服务端验证(协商缓存)—
no-store—禁止所有缓存机制—
immutable—提示浏览器资源永不变化,可安全使用缓存增强
优先级规则: Cache-Control 优先级高于 Expires;s-maxage 高于 max-age;private 与 public 决定缓存作用域。现代WebView全面优先使用 Cache-Control。

Expires 使用时间(如 Expires: Wed, 21 Oct 2025 07:28:00 GMT),依赖客户端时钟,容易因时间偏差导致缓存失效异常。因此推荐统一使用 Cache-Control 替代 Expires,仅在需要兼容HTTP/1.0老旧代理时保留 Expires 作为降级方案。

2.2 服务端配置实现

服务端是强缓存策略的起点。以下分别给出Nginx、Apache和Spring Boot的典型配置。

Nginx 配置示例

location ~* \.(js|css|png|jpg|woff2|svg)$ {
    expires 1y;
    add_header Cache-Control "public, max-age=31536000, immutable";
    add_header Vary "Accept-Encoding";
}

关键点分析:

  • expires 1y:自动生成 Expires 头(1年后),同时影响 Cache-Control: max-age。
  • immutable:告知WebView该资源内容永不改变,可跳过条件验证请求,大幅提升二次加载速度。
  • Vary:避免内容编码不一致导致缓存错乱。

Apache 配置示例

<FilesMatch "\.(js|css|png|jpg)$">
    Header set Cache-Control "public, max-age=31536000, immutable"
    Header set Expires "A31536000"
</FilesMatch>

Spring Boot (Java) 示例

@Configuration
public class StaticResourceConfig implements WebMvcConfigurer {
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/static/**")
                .addResourceLocations("classpath:/static/")
                .setCacheControl(CacheControl.maxAge(365, TimeUnit.DAYS)
                                  .cachePublic()
                                  .mustRevalidate());
    }
}
部署建议: 对于CSS/JS/图片等不常变更的静态资源,max-age 建议设置为 1 年(31536000 秒),并配合 immutable 标记。HTML文件建议设置 no-cache 以支持增量更新。

2.3 客户端协同配置

WebView 默认的缓存行为由操作系统网络栈决定,但开发者可以通过配置主动控制缓存策略。以下分 Android 和 iOS 给出实现。

Android WebView 缓存模式配置

WebView webView = new WebView(context);
WebSettings settings = webView.getSettings();

// 优先使用缓存策略:根据 Cache-Control 决定
settings.setCacheMode(WebSettings.LOAD_DEFAULT);

// 离线优先:缓存不存在时才走网络
// settings.setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK);

// 强制不使用缓存
// settings.setCacheMode(WebSettings.LOAD_NO_CACHE);

// 启用 HTTP 缓存
settings.setAppCacheEnabled(true);
settings.setDatabaseEnabled(true);
settings.setDomStorageEnabled(true);

iOS WKWebView 缓存策略配置

import WebKit

let config = WKWebViewConfiguration()
let url = URL(string: "https://example.com")!
let request = URLRequest(
    url: url,
    cachePolicy: .returnCacheDataElseLoad,
    timeoutInterval: 30
)

// 或使用协议默认策略
// let request = URLRequest(url: url,
//     cachePolicy: .useProtocolCachePolicy,
//     timeoutInterval: 30)

let webView = WKWebView(frame: .zero, configuration: config)
webView.load(request)
平台缓存模式行为描述推荐场景
AndroidLOAD_DEFAULT根据 Cache-Control 执行通用线上
AndroidLOAD_CACHE_ELSE_NETWORK优先本地缓存离线体验
AndroidLOAD_NO_CACHE完全绕过缓存强实时场景
iOSuseProtocolCachePolicy系统默认协议策略通用线上
iOSreturnCacheDataElseLoad优先本地缓存离线降级
iOSreloadIgnoringCacheData忽略所有缓存强制刷新
注意: 客户端缓存模式调整无法替代服务端响应头配置。若服务端未返回 Cache-Control,即使客户端设置 LOAD_DEFAULT 也不会产生强缓存效果。两端配置必须协同一致。

2.4 静态资源版本化策略

静态资源版本化是强缓存策略的“安全锁”。通过修改资源URL来强制WebView加载新版本,避免因长缓存导致用户无法获取更新。

三种主流版本化方案对比

策略实现方式优点缺点
文件名哈希app.8f3b2c1a.js内容变更URL必变,缓存失效彻底部署后需同步更新引用
版本号参数app.js?v=2.3.1实施简单,无需改文件名代理/CDN可能忽略参数
路径版本化/v2/js/app.js结构清晰,便于灰度版本迁移成本较高

推荐方案:文件名哈希 + 构建工具自动注入。以下为 webpack 的典型配置:

// webpack.config.js
const path = require('path');

module.exports = {
    entry: './src/index.js',
    output: {
        filename: '[name].[contenthash:8].js',
        path: path.resolve(__dirname, 'dist'),
        publicPath: '/static/'
    },
    plugins: [
        // 自动将哈希文件名注入 HTML
        new HtmlWebpackPlugin({
            template: './src/index.html'
        })
    ]
};

生成效果示例:

<!-- 构建前 -->
<script src="/static/app.js"></script>

<!-- 构建后 -->
<script src="/static/app.8f3b2c1a.js"></script>
版本化与强缓存的协同逻辑: 资源文件名随内容哈希变化 → WebView 视为全新资源 → 服务端返回新的 Cache-Control 头 → 强缓存策略对新资源生效。旧资源在缓存自然淘汰或手动清理后释放。

2.5 调试方法与验证

强缓存策略是否正确生效,需要通过浏览器开发工具或抓包工具验证。

  • Chrome DevTools → Network 面板: 查看 Size 列显示 (disk cache) 或 (memory cache) 表示强缓存命中;Status 为 200 (from cache)。
  • 响应头检查: 确认 Cache-Control 和 Expires 字段值与服务端配置一致。
  • 版本化验证: 更新资源后确认HTML中引用URL的哈希值发生变化,且网络请求为新URL。
  • 禁用缓存测试: 在 DevTools 中勾选 Disable cache 模拟首次访问,验证完整请求链路。
# 使用 curl 验证响应头
curl -I https://example.com/static/app.8f3b2c1a.js

# 预期返回
# HTTP/2 200
# cache-control: public, max-age=31536000, immutable
# expires: Thu, 01 Jan 2026 00:00:00 GMT

2.6 性能与稳定性优化

  • 缓存命中率监控: 通过 performance API 或 WebView 的 loadResource 回调统计缓存命中率,目标 > 90%。
  • 存储配额管理: Android 默认缓存上限为应用可用空间的 1/1000,iOS 为 50MB。超限时自动淘汰最早资源。
  • 紧急版本回退: 当线上出现严重Bug时,可通过服务端下发 Cache-Control: no-cache 使关键资源立即失效。
  • 避免过度缓存: HTML、API 响应等动态资源不适合长缓存,应设置 no-cache 或短 max-age(如 60 秒)。
  • CDN 缓存协同: 若使用 CDN,需确保 s-maxage 与 max-age 合理配合,避免 CDN 缓存与客户端缓存不一致。
强缓存策略决策与协同流程 强缓存策略决策与协同流程 用户请求静态资源 检查本地缓存存在 读取 Cache-Control / Expires 是否在有效期内? 是 否 命中强缓存,使用本地副本 未命中,请求服务端获取 资源版本化哈希校验 版本化确保内容变更后 缓存自动失效并更新
生产环境最佳实践: 将 Cache-Control: public, max-age=31536000, immutable 与文件名哈希配合使用,可实现“发布后秒级更新,更新前零网络请求”的理想缓存效果。HTML文档使用 no-cache 确保每次加载都获取最新资源列表。