我正在构建 ReactJs PWA,但在检测 iOS 上的更新时遇到问题。
在 Android 上,一切都运行良好,所以我想知道这一切是否与 iOS 对 PWA 的支持有关,或者是否我对 Service Worker 的实现不好。
这是我到目前为止所做的事情:
构建流程和托管
我的应用程序是使用 webpack 构建并托管在 AWS 上。大多数文件(js/css)都是在其名称中使用一些哈希值构建的,这些哈希值是根据其内容生成的。对于那些不是的(应用程序 list 、index.html、sw.js),我确保AWS为它们提供一些缓存控制 header 以防止任何缓存。一切都通过 https 提供。
服务人员
我使这个尽可能简单:除了为我的应用程序外壳预缓存之外,我没有添加任何缓存规则:
workbox.precaching.precacheAndRoute(self.__precacheManifest || []);
服务人员注册
Service Worker 的注册发生在主 ReactJs App 组件中的 componentDidMount() 生命周期 Hook 中:
componentDidMount() {
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/sw.js')
.then((reg) => {
reg.onupdatefound = () => {
this.newWorker = reg.installing;
this.newWorker.onstatechange = () => {
if (this.newWorker.state === 'installed') {
if (reg.active) {
// a version of the SW is already up and running
/*
code omitted: displays a snackbar to the user to manually trigger
activation of the new SW. This will be done by calling skipWaiting()
then reloading the page
*/
} else {
// first service worker registration, do nothing
}
}
};
};
});
}
}
Service Worker 生命周期管理
根据Google documentation about service workers ,当导航到范围内的页面时,应该检测到新版本的 Service Worker。但作为单页应用程序,一旦加载应用程序,就不会发生硬导航。
我找到的解决方法是 Hook 到react-router并监听路由更改,然后手动要求注册的服务工作人员更新自身:
const history = createBrowserHistory(); // from 'history' node package
history.listen(() => {
if ('serviceWorker' in navigator) {
navigator.serviceWorker
.getRegistration()
.then((reg) => {
if (!reg) {
return null;
}
reg.update();
});
}
});
实际行为
在上面显示的代码中到处扔一堆alert(),这就是我观察到的:
- 将 pwa 添加到主屏幕后首次打开时,Service Worker 在 Android 和 iOS 上按预期注册
- 在保持应用程序打开的同时,我在 AWS 上部署了新版本。感谢我的历史监听器,在应用程序中导航会触发手动更新。找到新版本,并在后台安装。然后我的 snackbar 就会显示出来,我可以触发切换到新的软件。
- 现在我关闭应用程序并在 AWS 上部署新版本。再次打开应用程序时:
- 在 Android 上,当 Android 重新加载页面时,会立即发现更新
- iOS 没有,因此我需要在应用程序中导航以查找历史监听器以触发更新搜索。这样做时,会发现更新
- 此后,对于两个操作系统,我的 snackbar 都会显示,我可以触发切换到新的软件
- 现在我关闭应用程序并关闭手机。部署新版本后,我再次启动它们并打开应用程序:
- 在 Android 上,就像以前一样,页面会重新加载以检测更新,然后显示 snackbar 等。
- 在 iOS 上,我在应用内导航,我的监听器会触发更新搜索。 但这一次,新版本从未找到,我的
onupdatefound事件处理程序也从未触发
阅读this post on Medium from Maximiliano Firtman看来,iOS 12.2 为 PWA 带来了新的生命周期。据他介绍,当应用程序长时间处于空闲状态或在设备重新启动期间,应用程序状态以及页面都会被终止。
我想知道这是否是我的问题的根本原因,但到目前为止我还没有找到任何人遇到同样的问题。
请您参考如下方法:
经过大量的挖掘和调查,我终于找到了我的问题所在。
根据我的观察,我认为 Android 和 iOS 处理 PWA 生命周期以及 Service Worker 的方式略有不同。
在 Android 上,重新启动后启动应用程序时,启动应用程序和搜索 Service Worker 的更新(由于重新加载页面时发生的硬导航)看起来像是 2 个并行完成的任务。通过这样做,应用程序有足够的时间订阅现有的 Service Worker,并在找到新版本的 Service Worker 之前定义一个 onupdatefound() 处理程序。
另一方面,对于 iOS,似乎当您在设备重新启动后启动应用程序时(或长时间未使用它后,请参阅主要主题中链接的 Medium 文章),iOS 会触发搜索在启动您的应用程序之前进行更新。如果发现更新,则会安装该更新,并在应用实际启动之前进入“等待”状态。这可能是显示闪屏时发生的情况...... 因此,最后,当您的应用程序最终启动并且您订阅现有的 Service Worker 来定义您的 onupdatefound() 处理程序时,更新已经安装并正在等待控制客户端。
这是我注册服务 worker 的最终代码:
componentDidMount() {
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/sw.js')
.then((reg) => {
if (reg.waiting) {
// a new version is already waiting to take control
this.newWorker = reg.waiting;
/*
code omitted: displays a snackbar to the user to manually trigger
activation of the new SW. This will be done by calling skipWaiting()
then reloading the page
*/
}
// handler for updates occuring while the app is running, either actively or in the background
reg.onupdatefound = () => {
this.newWorker = reg.installing;
this.newWorker.onstatechange = () => {
if (this.newWorker.state === 'installed') {
if (reg.active) {
// a version of the SW already has control over the app
/*
same code omitted
*/
} else {
// very first service worker registration, do nothing
}
}
};
};
});
}
}
注意:
我还摆脱了我用来触发搜索每次路线更改更新的历史记录的监听器,因为这似乎有点矫枉过正。 现在,我依靠页面可见性 API 在每次应用获得焦点时触发此搜索:
// this function is called in the service worker registration promise, providing the ServiceWorkerRegistration instance
const registerPwaOpeningHandler = (reg) => {
let hidden;
let visibilityChange;
if (typeof document.hidden !== 'undefined') { // Opera 12.10 and Firefox 18 and later support
hidden = 'hidden';
visibilityChange = 'visibilitychange';
} else if (typeof document.msHidden !== 'undefined') {
hidden = 'msHidden';
visibilityChange = 'msvisibilitychange';
} else if (typeof document.webkitHidden !== 'undefined') {
hidden = 'webkitHidden';
visibilityChange = 'webkitvisibilitychange';
}
window.document.addEventListener(visibilityChange, () => {
if (!document[hidden]) {
// manually force detection of a potential update when the pwa is opened
reg.update();
}
});
return reg;
};






