我正在构建ReactJs PWA,但是在iOS上无法检测更新。
在Android上,一切工作正常,所以我想知道这是否与iOS对PWA的支持有关,或者我对服务工作者的实现不好吗?
这是我到目前为止所做的:
构建过程和托管
我的应用是使用webpack构建的,并托管在AWS上。大多数文件(js / css)的名称中都包含一些哈希值,这些哈希值是根据其内容生成的。对于那些不是(应用清单,index.html,sw.js)的应用程序,我确保AWS为它们提供了一些Cache-Control标头,以防止任何缓存。一切都通过https提供。
服务人员
我尽可能简化了这一过程:除了为我的应用程序外壳程序预缓存外,我没有添加任何缓存规则:
workbox.precaching.precacheAndRoute(self.__precacheManifest || []);
服务人员注册
服务工作者的注册发生在主要的ReactJs App组件的componentDidMount()生命周期挂钩中:
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
}
}
};
};
});
}
}
服务人员生命周期管理
根据Google documentation about service workers,当导航到范围内页面时,应检测到新版本的Service Worker。但是作为单页应用程序,加载该应用程序后不会发生任何困难的导航。
我为此找到的解决方法是挂接到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()
,这是我观察到的:
onupdatefound
事件处理程序 阅读this post on Medium from Maximiliano Firtman,似乎iOS 12.2为PWA带来了新的生命周期。据他介绍,当应用程序长时间闲置或在设备重启期间,应用程序状态以及页面均被杀死。
我想知道这是否可能是我遇到问题的根本原因,但是到目前为止,我找不到任何遇到同样麻烦的人。
答案 0 :(得分:2)
因此,经过大量的调查和研究,我终于发现了我的问题。
根据我的观察,我认为Android和iOS处理PWA生命周期以及服务工作者的方式略有不同。
在Android上,重新启动后启动应用程序时,看起来像是启动应用程序并搜索服务工作者的更新(由于重新加载页面时会发生硬导航),这是并行完成的两个任务。通过这样做,该应用程序有足够的时间来订阅已经存在的服务程序,并在找到新版本的服务程序之前定义一个onupdatefound()
处理程序。
另一方面,在iOS设备上,似乎在设备重启后(或长时间不使用后,请参见主要主题中的中篇文章)启动应用程序时,iOS会触发搜索在启动您的应用之前进行更新。而且,如果找到更新,则将安装该更新并在实际启动该应用之前之前输入其“等待”状态。显示初始屏幕时可能会发生这种情况...
因此,最后,当您的应用程序最终启动并且您订阅了已经存在的服务工作程序以定义您的onupdatefound()
处理程序时,此更新已经安装并且正在等待控制客户端。
这是我注册服务工作者的最终代码:
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
}
}
};
};
});
}
}
注意:
我也摆脱了过去的历史,我曾经触发过对每条路线变更进行更新搜索的历史记录,因为这似乎过分了。 现在,我依靠Page Visibility 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;
};
答案 1 :(得分:1)
正如Speckles所指出的(感谢您为我省去的麻烦),iOS在启动应用程序之前先安装了新的软件。因此,SW无法获得“安装”状态。
解决方法:检查注册是否处于等待状态,然后进行处理。
I've made an (untested) example of handling this.-默认CRA SW的mod。