跳转到内容

Manifest · 概念

已安装的 PWA 如何获取清单更新

发布于 更新于

已安装的 PWA 保留一份安装时所用清单的本地副本。Chrome 按计划重新抓取线上清单,与这份副本比较, 并且只在应用的所有窗口都关闭之后才应用变化。哪些字段触发重新检查、哪些字段随后被应用,桌面端 Chrome 与 Android 上的 Chrome 并不相同,而且桌面端路径完全无法更新图标。

这里记录的是 Chrome 的行为,由 Chrome 团队在 web.dev 的 How Chrome handles updates to the web app manifest 一文中描述;Edge 154 作为 Chromium 浏览器共用桌面端路径。Apple 没有为 Safari 27 的 主屏幕应用或 Dock 应用发布对应说明,Firefox 157 不从清单安装 Web 应用,所以本条目不对二者做 任何断言。

已安装应用的每次启动,以及在浏览器标签页里对站点的每次访问,都会让 Chrome 检查上一次比较清单的 时间。如果那是在浏览器上次启动之前,或者已经超过 24 小时,Chrome 就重新抓取清单。抓回的值与 本地相同时,计时器重置,不发生别的事。

在桌面端,name、short_name、display、scope、shortcuts、start_url、theme_color、 file_handlers 中任何一项发生变化都会把新清单排入队列;应用的每个窗口都关闭后安装新清单,然后 除 icons 之外的所有字段都被应用,桌面端 Chrome 不更新图标。此外还有两条规则:start_url 的 变化只在清单声明了 id 时才被应用,因为没有 id 时旧的 start_url 就是应用的身份;display 从 browser 改为 standalone 不会改变那些选择在标签页中打开应用的用户,用户的窗口偏好优先于 清单。

在 Android 上同样每 24 小时检查一次,但触发列表是 name、short_name、icons、 background_color、display、orientation、scope、shortcuts、start_url、theme_color 与 share_target,而应用变化意味着向 Google 的铸造服务器请求一个新的 WebAPK。Chrome 会等到 应用关闭、设备在充电且连接 Wi-Fi 之后才去请求;新 WebAPK 安装后,包括图标在内的每个字段都是 最新的。服务器不可达时 Chrome 会退避,最慢每 30 天检查一次。

重命名或移动清单文件会切断这条链:Chrome 重新抓取的是安装时的那个 URL,那里返回 404 就意味着 永远发现不了更新。保持清单 URL 稳定,只改它的内容。

前两个示例是清单改动及其对已安装用户的影响;第三个是如何不等一天就看到结果。

迁移启动 URL 而不产生第二个应用

Section titled “迁移启动 URL 而不产生第二个应用”

团队把应用从 / 迁到 /app/。因为清单声明了 id,Chrome 把改过的清单视为同一个应用,在下一次 检查后应用新的 start_url;没有 id 的话,这个改动在桌面端会被忽略,已安装应用仍然启动 /。

{
"id": "/",
"name": "Ledger",
"start_url": "/app/?source=installed",
"scope": "/",
"display": "standalone"
}

在迁移之前、在一个不改别的东西的发布里先加上 id:id 必须已经存在于已安装的副本中,之后的 start_url 变化才会被识别。

更换主题色并知道用户何时能看到

Section titled “更换主题色并知道用户何时能看到”

theme_color 在两份触发列表上都有,所以品牌换色无需重装就能到达已安装用户。时间线取决于检查 节奏,而不是部署时刻。

{
"name": "Ledger",
"theme_color": "#0b5fff",
"background_color": "#ffffff"
}

整天开着应用的桌面用户,要等某次检查发现变化、再关闭最后一个窗口之后才看到新颜色;Android 用户还需要充电加 Wi-Fi 才能让 WebAPK 重新铸造。预期是一两天,很少在 Wi-Fi 下充电的设备更久。

Chrome 为开发者提供两条绕过 24 小时节流的路径:重启浏览器(about://restart 会重置计时器), 或带 --disable-manifest-update-throttle 启动。下面的脚本是应用内的配套检查:它把页面能抓到的 清单与烘进已安装 start_url 里的版本戳比较,告诉测试者已安装副本是否仍是旧的。

async function installedManifestIsStale() {
const installedVersion = new URL(location.href).searchParams.get('v'); // 来自 start_url
const live = await fetch('/manifest.webmanifest', { cache: 'no-store' }).then((r) => r.json());
const liveVersion = new URL(live.start_url, location.origin).searchParams.get('v');
if (!installedVersion || !liveVersion) return false; // 没有版本戳:无可比较
return installedVersion !== liveVersion;
}

页面读不到浏览器存储的副本;start_url 里的版本戳是页面唯一能观察到的已安装清单的一部分, 这也是为什么在 start_url 里加 ?v= 成了常见的测试约定。

规范

规范状态
Web Application Manifest: id memberW3C