我正在构建一个ReactJs PWA,但我在iOS上检测更新时遇到问题。
在Android上一切都很好,所以我想知道所有这些是否与iOS支持PWA有关,或者我的服务工作者的实现是不是很好。
这是我到目前为止所做的:
构建流程和托管
我的应用程序使用webpack构建并托管在AWS上。大多数文件(js / css)都是在其名称中使用一些哈希构建的,这些哈希是根据其内容生成的。对于那些不是(app manifest,index.html,sw.js)的人,我确保AWS为他们提供了一些阻止任何缓存的Cache-Control头。一切都通过https提供。
服务人员
我保持这个尽可能简单:除了我的app-shell的预缓存之外,我没有添加任何缓存规则:
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,在导航到范围内页面时应检测新版本的服务工作者。但作为单页面应用程序,一旦应用程序加载,就不会发生硬导航。
我找到的解决方法是挂钩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带来了新的生命周期。根据他的说法,当应用程序长时间闲置或重启设备时,应用程序状态以及页面都会被杀死。
我想知道这可能是我问题的根本原因,但到目前为止我找不到任何有同样问题的人。
经过大量的挖掘和调查,我终于发现了我的问题。
从我能够观察到的情况来看,我认为Android和iOS处理PWA生命周期以及服务工作者的方式有所不同。
在Android上,在重新启动后启动应用程序时,看起来启动应用程序并搜索服务工作者的更新(由于重新加载页面时出现硬导航)是并行完成的2个任务。通过这样做,应用程序有足够的时间订阅现有的服务工作者并在找到新版本的服务工作者之前定义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;
};