移动端页面缓存问题是指页面的静态资源(如图片、JS 和 CSS 文件)在浏览器中被缓存后,用户在下次访问时可以直接从本地获取缓存数据,而不需要每次都从服务器重新获取,不过这样可能会导致页面不能正确地更新或者加载最新的内容。为了解决这个问题,我们可以采用一些缓存控制策略来解决。
一、缓存的基本原理
1. 什么是浏览器缓存
浏览器缓存是指当客户端向服务器请求资源时,会先抵达浏览器缓存,如果浏览器有“要请求资源”的副本,就可以直接从浏览器缓存中提取而不是从原始服务器中提取这个资源。
浏览器缓存分为:
- 协议缓存:也叫浏览器缓存、网页缓存,通过协议头里的 Cache-Control、Last-Modified、Expires、Etag等控制文件缓存;
- 应用缓存:缓存HTML5程序,让Web应用程序可以离线运行;
- 移动端APP中的内嵌HTML5缓存:也可以理解为 webview 中的 缓存;
协议缓存和 webview中缓存的场景出现的比较多,本文中只聊这两种缓存。
2. 缓存行为分析
首先,我们需要了解 WebView 和浏览器是如何缓存资源的。浏览器和 WebView 会缓存网络请求的资源,以提高页面加载速度,并减少网络流量。这种缓存机制对于一些资源是有益的,比如图片、样式文件、JavaScript 文件等,它们往往没有频繁变化,缓存可以节省加载时间和带宽。
然而,WebView 和浏览器也有缓存 HTML 文件的行为,尤其是当 URL 不发生变化时。此时,即使前端代码已经更新,浏览器或 WebView 可能会加载缓存中的旧版 HTML 和 JavaScript 资源,导致页面展示的内容不是最新的。
3. 浏览器缓存的流程
- 首次请求:浏览器发起 HTTP 请求并从服务器获取资源(HTML、CSS、JS、图片等),服务器响应时会带上相关的缓存头(如 Cache-Control)。
- 缓存保存:浏览器根据响应头部信息将资源缓存到本地存储(如内存缓存或硬盘缓存)。
- 后续请求:当用户再次访问相同资源时,浏览器会检查缓存是否有效,如果缓存未过期或未被清除,则直接使用缓存中的资源。如果资源过期,浏览器会向服务器发起验证请求,服务器通过 ETag 或 Last-Modified 确认资源是否变化,如果没有变化,浏览器将继续使用缓存的副本。
4. 哪些资源会出现缓存
一是静态资源(js、css、image),二是 HTML 本身,都可能会被缓存。
- 如果资源已被缓存且没有过期,资源的 URL 没有发生变化,浏览器会先加载缓存的资源。如 html 中引用 a.js, 用户刷新页面时,a.js 会从浏览器缓存中获取,而不是从服务器中获取
- 如果 js、css、img 静态资源做了版本号处理,但 HTML 本身没有重新请求,也是会导致渲染原来的HTML页面
- 需要我们自动检测更新,并自动更新的场景(这个比较适用于场景2 和 场景3。因为场景1通常情况下我们不考虑):
- 场景1:用户开启着的浏览器,未刷新过页面;
- 场景2:我们提供给任意第三方的嵌入式 js 文件,如 webapp.js,第三方未知,且我们也无法修改第三方的 html 代码;
- 场景3:移动端 APP 嵌入到一级 NavBar 中的 H5 页面,为了用户体验设计,切换菜单时,并不销毁 webview,所以现象就是不杀掉 APP,H5页面不会刷新。
二、HTTP 协议缓存
1. HTTP 缓存的基本概念
HTTP 协议缓存机制是指通过 HTTP 协议头里的 Cache-Control(或 Expires)和 Last-Modified(或 Etag)等字段来控制资源缓存的机制。
HTTP 协议提供了几个重要的参数,用来控制缓存的行为:
- Cache-Control:最常用的缓存控制头部。通过设置不同的值来告诉浏览器是否缓存资源,以及缓存多长时间。例如:
- Cache-Control: no-cache:每次请求都需要检查资源是否更新(强制验证缓存)。
- Cache-Control: no-store:禁止缓存,浏览器每次都从服务器加载资源。
- Cache-Control: max-age=:指定缓存的最大有效期(单位为秒),过期后需要重新从服务器获取资源。
- Cache-Control: public:资源可以被任何缓存(如浏览器或代理服务器)缓存。
○Cache-Control: private:资源只能被浏览器缓存,不能被共享缓存(如 CDN 或代理服务器)缓存。
- Expires:指定资源过期的日期和时间,表示资源在此时间之后不再有效,需重新获取。与 Cache-Control 配合使用时,Cache-Control 优先级更高。
- ETag 和 Last-Modified:用于浏览器和服务器之间的资源验证。如果资源没有变化,浏览器可以使用缓存的副本;如果变化了,服务器会返回新的版本。
2. HTTP 缓存工作流程
- 浏览器请求资源:当浏览器向服务器请求资源时,服务器可以通过 Cache-Control 和 Expires 等头部告诉浏览器资源是否可以缓存,缓存多久,是否需要重新验证等。
- 浏览器缓存资源:如果服务器允许缓存资源,浏览器会把资源存储到本地缓存中,并标记资源的 Last-Modified 时间和 ETag(如果有)。
- 浏览器重新请求资源:当浏览器下次请求相同的资源时,它会根据缓存策略进行处理:
- 如果缓存没有过期,浏览器会直接从缓存中读取资源,避免网络请求。
- 如果缓存过期,浏览器会重新请求资源,并通过 If-Modified-Since 或 If-None-Match 头向服务器询问资源是否有变化。
- 服务器响应:服务器根据缓存验证结果决定是否返回新的资源。如果资源没有变化,服务器会返回 304 Not Modified 状态,告诉浏览器继续使用缓存。如果资源已修改,服务器会返回新的资源。
3. HTTP 缓存的类型
-
强缓存(强制缓存):
强缓存通过 Cache-Control 和 Expires 控制,浏览器在缓存有效期内直接使用缓存,不会发起请求。示例:Cache-Control: max-age=3600 表示资源可以缓存 1 小时。
-
协商缓存(验证缓存):
协商缓存依赖于 Last-Modified 和 ETag 头部,浏览器每次请求时会带上缓存的时间或标识符,服务器验证资源是否发生变化。
- 如果没有变化,服务器返回 304 Not Modified,浏览器继续使用缓存。
- 如果资源变化,服务器返回新的资源。
4. HTTP 缓存的优化策略
- 使用合理的缓存过期时间:根据资源的更新频率设置合理的缓存时间,例如图片和 CSS 可以设置较长的缓存时间,而 API 请求可以设置较短的缓存时间。
- 版本控制:通过在资源的 URL 中添加版本号或时间戳,使浏览器识别到资源变化并重新加载。
- 利用 CDN(内容分发网络):CDN 可以将资源缓存到不同地域的服务器上,减少请求的延迟并提高资源的加载速度。
- 清除过期缓存:通过服务器配置和客户端的缓存管理,定期清除过期的缓存,避免浪费存储空间和导致不一致的资源问题。
三、WebView缓存
1. 缓存概述
WebView 是一种嵌入式浏览器控件,让 Web 应用嵌入到原生 App 中,通过它可以在原生应用中加载和展示网页。由于 WebView 像一个浏览器一样呈现网页,因此它也有自己的缓存机制,用于存储网页内容、资源(如图片、CSS、JavaScript 等)以及请求的响应,以提高页面加载速度,减少网络请求。
然而,WebView 的缓存机制有时会导致用户看到的是旧版本的页面或资源,尤其是在 H5 发版后,iOS 端用户可能仍然加载到缓存的旧页面,造成了不一致的体验。
2. 缓存的工作原理
- 资源加载
- 当一个网页在 WebView 中加载时,它会检查是否已经缓存了该网页的资源。
- 如果缓存中有相关资源,WebView 会从缓存中加载,而不是从网络请求。
- 如果缓存没有资源,WebView 会向服务器发起请求并缓存返回的资源。
- 缓存控制
- WebView 会根据 HTTP 请求中的 Cache-Control、Expires 和 ETag 等头部信息来决定缓存的策略。
- 如果网页资源的缓存已过期或服务器要求不缓存资源,WebView 会重新请求资源。
- WebView 预加载缓存
- WebView 也可以支持 预加载缓存,即在后台提前加载并缓存网页资源,当用户需要时,页面可以更快速地加载。
- 存储位置
- WebView 会将缓存文件存储在设备的文件系统中。缓存存储的位置因平台而异,在 Android 中,通常缓存文件存储在应用的私有目录或 WebView 的默认目录中,在 iOS 中,则存储在沙盒内。
四、解决方案
如果希望在每次部署新版本时浏览器能强制重新加载新的 HTML 文件,而不是使用缓存中的旧版本,可以通过以下几种方式来确保 HTML 文件能够及时更新:
1. HTTP 配置
1.1 使用 Cache-Control
你可以在服务器端控制缓存策略,确保浏览器不会缓存资源。
- Cache-Control: no-cache:告诉浏览器每次请求时都需要验证资源是否已更新。如果资源改变,浏览器将重新加载;否则,使用缓存。
- Cache-Control: no-store:告诉浏览器完全不缓存任何资源,每次请求时都从服务器获取。
- Cache-Control: max-age=0:设置缓存过期时间为 0,实际上也会强制每次请求都从服务器获取资源。
1.2 使用 Expires: 0
Expires 头部可以设置资源的过期时间,0 或过去的时间可以确保资源被认为是过期的,浏览器每次都会从服务器拉取资源
Expires: 0
1.3 动态 URL 参数
通过在每个请求的 URL 上添加一个动态的查询参数,可以防止浏览器缓存资源。常见做法是添加一个时间戳或版本号。例如:
<script src="script.js?v=20250114"></script>
你可以使用当前时间戳或版本号作为查询参数,每次请求时都保证 URL 是唯一的,浏览器会认为这是新的请求,从服务器重新加载资源。
例如,生成 URL 的方式:
const url = "script.js?v=" + new Date().getTime();
1.4 使用 no-cache meta 标签(适用于 HTML 页面)
虽然 meta 标签不能完全替代 HTTP 头部,但你也可以通过在 HTML 中添加 no-cache 的 meta 标签来尽量避免缓存。
<meta http-equiv="Cache-Control" content="no-cache"><meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Expires" content="0">
2. Nginx配置
通过修改 Nginx 配置文件来设置 HTTP 头部,可以防止浏览器缓存 HTML 文件。以下是一个常见的配置示例:
2.1 禁止缓存文件
缓存HTM文件
在 Nginx 配置文件中,可以为 HTML 文件配置 Cache-Control、Pragma 和 Expires 头部来禁用缓存:
server {
listen 80;
server_name yourdomain.com;
location / {
# 禁止 HTML 缓存
add_header Cache-Control 'no-store, no-cache, must-revalidate, proxy-revalidate';
add_header Pragma 'no-cache';
add_header Expires '0';
# 其他配置
root /var/www/html;
index index.html;
}
# 其他 location 配置
}
配置解释:
- Cache-Control: no-store, no-cache, must-revalidate, proxy-revalidate:这个配置确保每次请求都会重新拉取 HTML 文件,且浏览器不会缓存它。
- Pragma: no-cache:这是为了兼容老旧浏览器,它会告诉浏览器不要缓存资源。
- Expires: 0:将资源的过期时间设置为 0,使其立即过期,从而避免缓存。
缓存其他静态资源(如 JS、CSS、图片)
如果你希望缓存其他静态资源(如 JavaScript、CSS 和图片),但不缓存 HTML 文件,可以针对这些资源配置不同的缓存策略。例如,你可以设置较长的缓存时间,缓存静态资源:
server {
listen 80;
server_name yourdomain.com;
# 禁止缓存 HTML 文件
location / {
add_header Cache-Control 'no-store, no-cache, must-revalidate, proxy-revalidate';
add_header Pragma 'no-cache';
add_header Expires '0';
root /var/www/html;
index index.html;
}
# 缓存 JS, CSS, 图片文件(设置 1 周缓存)
location ~* \.(js|css|jpg|jpeg|png|gif|svg|ico|woff|woff2|ttf)$ {
add_header Cache-Control 'public, max-age=604800'; # 缓存 1 周
}
}
配置解释:
- location ~* .(js|css|jpg|jpeg|png|gif|svg|ico|woff|woff2|ttf)$:这行配置用于匹配所有常见的静态资源文件(JavaScript、CSS、图片、字体等)。
- Cache-Control: public, max-age=604800:这会将静态资源缓存 7 天(604800 秒),从而减少对服务器的请求。
2.2 缓存策略与优化
为了提高 H5 页面的性能和用户体验,开发者可以采取一些缓存优化策略:
- 合理设置缓存头:通过设置适当的 Cache-Control、Expires、ETag 等 HTTP 头部来控制资源的缓存行为。
- 版本化静态资源:通过 URL 中添加版本号(如 script.js?v=1.0.0)来避免缓存带来的问题。
- Service Worker 缓存:利用 Service Worker 来精细控制哪些资源缓存,哪些资源不缓存,并提供离线功能。
●懒加载和预加载:将不常用的资源延迟加载,常用资源预加载,提高页面响应速度。
3. 手动清除缓存
如果你的服务器配置无法保证自动清除 HTML 缓存,或者你不希望修改文件名,你还可以在用户访问网页时通过 JavaScript 强制清除浏览器缓存,或者通过 meta 标签来控制缓存。
3.1 HTML 标签
你可以在 index.html 文件的 标签中加入以下 meta 标签来告知浏览器不要缓存页面:
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Expires" content="0">
这会告诉浏览器:
- Cache-Control: no-cache:每次请求都需要检查服务器上是否有新的内容。
- no-store:禁止缓存文件,确保每次都从服务器获取。
- Expires: 0:设置过期时间为 0,表示文件立即过期。
3.2 JavaScript 通过控制缓存
在某些情况下,可以通过 JavaScript 在加载页面时清除缓存或强制加载新的资源。例如,使用 window.location.reload(true) 强制浏览器从服务器加载页面而不是从缓存中加载:
// 动态清除缓存并重新加载页面
window.location.reload(true);
4.动态参数策略
一种常见的解决方案是 给 URL 加上动态参数,如时间戳或随机数。每次加载时,URL 发生变化,即使 URL 模式相同,由于参数不同,浏览器会认为这是一个新的请求,从而绕过缓存,重新加载最新的 HTML 文件和其他资源。
const timestamp = new Date().getTime();
const url = `https://example.com/page?timestamp=${timestamp}`;
每次页面加载时,都会生成一个不同的 URL,从而避免缓存。