您已发布 PWA:一些用户通过浏览器使用 PWA,其他用户则在自己的设备上安装 PWA。在更新应用时,请务必遵循最佳实践以避免误操作。
您可以更新:
- 应用数据。
- 设备已缓存资产。
- Service Worker 文件或其依赖项。
- 清单元数据。
我们来看看其中每个元素的最佳做法。
更新数据
如需更新数据(例如存储在 IndexedDB 中的数据),您可以使用 Fetch、WebRTC 或 WebSockets API 等工具。如果您的应用支持任何离线功能,请务必也及时更新支持这些功能的数据。
在兼容的浏览器中,您不仅可以选择在用户打开 PWA 时同步数据,还可以在后台进行数据同步。这些选项包括:
- 后台同步:保存失败的请求,并使用 Service Worker 的同步功能重试这些请求。
- 网络定期后台同步:定期在后台(在特定时间)同步数据,让应用能够提供更新后的数据(即使用户尚未打开应用)。
- 后台提取:下载大型文件,即使 PWA 关闭也是如此。
- 网络推送:从服务器发送消息以唤醒 Service Worker 并通知用户。这通常称为“推送通知”。此 API 需要用户的许可。
所有这些 API 都是从 Service Worker 上下文执行的。目前只能在基于 Chromium 的浏览器、Android 和桌面设备操作系统上使用。当您使用其中一个 API 时,您可以在 Service Worker 线程中运行代码;例如,从您的服务器下载数据并更新 IndexedDB 数据。
更新资产
更新资源包括对用于呈现应用界面的文件(例如 HTML、CSS、JavaScript 和图片)所做的任何更改。例如,应用逻辑发生变化、界面中的图片或 CSS 样式表。
更新模式
下面是处理应用更新的一些常见模式,但您可以随时根据自己的需求自定义流程:
- 完全更新:每一次更改(哪怕只是细微更改)都会触发整个缓存内容的替换。此模式会模拟特定于设备的应用处理更新的方式,因此会占用更多带宽和时间。
- 更新已更改的资源:在缓存中仅替换自上次更新后更改的资源。它通常使用 Workbox 等库来实现。此过程涉及创建缓存文件列表、文件的哈希表示法以及时间戳。借助这些信息,Service Worker 将此列表与缓存资产进行比较,并决定要更新的资产。
- 单个资源更新:每个资源在发生更改时单独进行更新。传送一章中描述的“过时同时重新验证”策略是单独更新资源的一个示例。
何时更新
另一个好的做法是找一个合适的时间来检查并应用这些更新。您可以做出如下选择:
- Service Worker 唤醒时。此刻没有要监听的事件,但浏览器在唤醒时会在 Service Worker 的全局范围内执行任何代码。
- 浏览器加载页面后,在 PWA 的主窗口上下文中,避免应用加载速度变慢。
- 后台事件触发时,例如 PWA 收到推送通知或后台同步触发时。然后,您可以更新缓存,这样您的用户会在下次打开应用时看到相应资源的新版本。
实时更新
您还可以选择在应用处于打开(发布)或关闭状态时应用更新。采用应用关闭方法后,即使应用已下载新的资源,也不会做出任何更改,并会在下次加载时使用新版本。
实时更新意味着,一旦资源在缓存中进行了更新,PWA 会立即替换当前加载中的资源。这是一项复杂任务,本课程未介绍。一些有助于实现此更新的工具包括 livereload-js 和 CSS 资源更新 CSSStyleSheet.Replace() API。
更新 Service Worker
当 Service Worker 或其依赖项发生更改时,浏览器会触发更新算法。浏览器通过对缓存文件与来自网络的资源之间的逐字节比较来检测更新。
然后,浏览器尝试安装新版 Service Worker,而新 Service Worker 将处于 waiting 状态,如 Service Worker 一章中所述。新安装将为新 Service Worker 运行 install
事件。如果您在该事件处理脚本中缓存资源,资源也会重新缓存。
检测 Service Worker 的变化
为了检测新的 Service Worker 是否已准备就绪并已安装,我们会使用 Service Worker 注册中的 updatefound
事件。当新的 Service Worker 开始安装时会触发此事件。我们需要等待 statechange
事件的状态变为 installed
;请参阅以下内容:
async function detectSWUpdate() {
const registration = await navigator.serviceWorker.ready;
registration.addEventListener("updatefound", event => {
const newSW = registration.installing;
newSW.addEventListener("statechange", event => {
if (newSW.state == "installed") {
// New service worker is installed, but waiting activation
}
});
})
}
强制覆盖
新 Service Worker 将安装,但默认等待激活。等待可防止新 Service Worker 接管可能与新版本不兼容的旧客户端。
尽管不建议这样做,但新 Service Worker 可以跳过该等待期,并立即启动激活。
self.addEventListener("install", event => {
// forces a service worker to activate immediately
self.skipWaiting();
});
self.addEventListener("activate", event => {
// when this SW becomes activated, we claim all the opened clients
// they can be standalone PWA windows or browser tabs
event.waitUntil(clients.claim());
});
controllerchange
事件会在控制当前页面的 Service Worker 发生变化时触发。例如,新工作器跳过了等待,成为新的活跃工作器。
navigator.serviceWorker.addEventListener("controllerchange", event => {
// The service worker controller has changed
});
更新元数据
您还可以更新应用的元数据,这些元数据主要在Web 应用清单中设置。例如,更新应用的图标、名称或起始网址,或者添加应用快捷方式等新功能。 但是,如果用户已在其设备上安装了带有旧图标的应用,会发生什么情况呢?他们如何以及何时获得更新的版本?
答案取决于平台。让我们来了解一下可用的选项。
iOS、iPadOS 和 Android 浏览器上的 Safari
在这些平台上,获取新清单元数据的唯一方法是通过浏览器重新安装应用。
Android 版 Google Chrome 及 WebAPK
当用户使用 Google Chrome 安装您的 Android 版 PWA 并激活 WebAPK 后(大部分 Chrome PWA 安装),系统会根据算法检测并应用更新。如需了解详情,请参阅此清单更新一文。
关于此流程的一些其他说明:
如果用户未打开您的 PWA,则其 WebAPK 将不会更新。 如果服务器没有通过清单文件做出响应(出现 404 错误),那么即使用户打开 PWA,Chrome 至少也会在 30 天内不会检查更新。
在 Android 设备上的 Chrome 中前往 about:webapks
,以查看“需要更新”的状态标志并请求更新。在工具和调试章节中,您可以详细了解这款调试工具。
采用 WebAPK 的 Android Samsung Internet
此流程与 Chrome 版本类似。在这种情况下,如果 PWA 清单需要更新,那么在接下来的 24 小时内,系统会在创建更新后的 WebAPK 后通过 Wi-Fi 更新 WebAPK。
桌面版 Google Chrome 和 Microsoft Edge
在桌面设备上,当 PWA 启动时,浏览器会确定上次检查本地清单是否有更改的时间。如果自浏览器上次启动以来尚未检查该清单,或在过去 24 小时内未检查过该清单,浏览器将针对该清单提出网络请求,然后将其与本地副本进行比较。
如果更新所选房源,系统会在所有窗口关闭后触发更新。
提醒用户
某些更新策略需要重新加载客户端或客户端进行新的导航。您需要告知用户有更新在等着您,但要让用户有机会在最需要的时候更新页面。
如需通知用户,可选择以下选项:
- 使用 DOM 或 canvas API 在屏幕上呈现通知。
- 使用 Web Notifications API。此 API 是推送权限的一部分,用于在操作系统中生成通知。即使您不使用服务器上的推送消息传递协议,您也需要请求 Web 推送权限才能使用它。如果 PWA 未打开,这是我们唯一可用的选项。
- 使用 Badging API 在 PWA 的已安装图标中显示有可用更新
。
详细了解 Badging API
借助 Badging API,您可以使用标记编号或兼容的浏览器上的标记圆点标记 PWA 的图标。标记圆点是已安装图标内的一个小标记,表示应用中有等待安装的内容。
您需要对 navigator
对象调用 setAppBadge(count)
才能设置徽章编号。当您知道有更新来提醒用户时,可以从窗口或 Service Worker 的上下文中执行此操作。
let unreadCount = 125;
navigator.setAppBadge(unreadCount);
如需清除标志,请对同一对象调用 clearAppBadge()
:
navigator.clearAppBadge();