【问题标题】:ReactJs PWA not updating on iOSReactJs PWA 未在 iOS 上更新
【发布时间】:2019-04-08 21:03:52
【问题描述】:

我正在构建 ReactJs PWA,但在 iOS 上检测更新时遇到问题。

在 Android 上一切正常,所以我想知道这一切是否与 iOS 对 PWA 的支持有关,或者我的 service worker 实现不好。

这是我到目前为止所做的:

构建过程和托管

我的应用是使用 webpack 构建并托管在 AWS 上的。大多数文件(js/css)都是在其名称中使用一些哈希构建的,由其内容生成。对于那些不是(应用程序清单、index.html、sw.js),我确保 AWS 为它们提供一些 Cache-Control 标头以防止任何缓存。一切都通过 https 提供。

服务工作者

我让这个尽可能简单:我没有为我的 app-shell 添加任何缓存规则,除了预缓存:

workbox.precaching.precacheAndRoute(self.__precacheManifest || []);

服务工作者注册

Service Worker 的注册发生在 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
              }
            }
          };
        };
      });
  }
}

Service Worker 生命周期管理

根据Google documentation about service workers,导航到范围内页面时应检测到新版本的服务工作者。但作为单页应用,一旦加载应用,就不会发生硬导航。

我找到的解决方法是连接到 react-router 并监听路由变化,然后手动要求注册的 service worker 进行自我更新:

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(),这是我观察到的:

  1. 将 pwa 添加到主屏幕后第一次打开时,Service Worker 已按预期注册,在 Android 和 iOS 上
  2. 在保持应用程序打开的同时,我在 AWS 上部署了一个新版本。由于我的历史监听器,在应用程序中导航会触发手动更新。找到新版本,后台安装。然后我的快餐栏就会显示出来,我可以触发切换到新的软件。
  3. 现在我关闭应用程序并在 AWS 上部署新版本。再次打开应用程序时:
    • 在 Android 上,当 Android 重新加载页面时会立即找到更新
    • iOS 没有,所以我需要在应用程序中导航,以便我的历史监听器触发更新搜索。这样做时,会发现更新
    • 在这之后,对于两个操作系统,我的快餐栏都会显示,我可以触发切换到新的 SW
  4. 现在我关闭应用程序并关闭手机。部署新版本后,我再次启动它们并打开应用程序:
    • 在 Android 上,就像以前一样,重新加载检测到更新的页面,然后显示小吃栏等。
    • 在 iOS 上,我在应用程序中导航,我的侦听器触发搜索更新。 但这一次,新版本永远找不到,我的onupdatefound事件处理程序也永远不会触发

阅读this post on Medium from Maximiliano Firtman,iOS 12.2 似乎为 PWA 带来了新的生命周期。据他介绍,当应用长时间处于空闲状态或设备重启期间,应用状态和页面都会被杀死。

我想知道这是否可能是我的问题的根本原因,但到目前为止我找不到遇到同样问题的人。

【问题讨论】:

  • What’s new on iOS 12.2 for Progressive Web Apps 也有更新。是的,到目前为止,iOS 上的 PWA 最糟糕的问题——如果不是最糟糕的——就是重新加载问题。我认为这是您问题的根本原因,因为 iOS 中新的 PWA 生命周期。

标签: ios reactjs progressive-web-apps


【解决方案1】:

所以经过大量的挖掘和调查,我终于发现了我的问题。

据我观察,我认为 Android 和 iOS 处理 PWA 生命周期的方式以及服务工作者的方式略有不同。

在 Android 上,重启后启动应用程序时,启动应用程序和搜索 Service Worker 的更新(由于重新加载页面时发生的硬导航)似乎是两个并行完成的任务。通过这样做,应用有足够的时间订阅现有的 Service Worker,并在找到新版本的 Service Worker 之前定义一个 onupdatefound() 处理程序。

另一方面,对于 iOS,似乎当您在设备重启后启动应用程序(或长时间不使用后,请参阅主题中链接的 Medium 文章)时,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;
};

【讨论】:

  • 你可以修改create react app service worker并在github上做一个示例
  • Ehsan,以我的回答为例。
  • @Ehsansarshar 这似乎是专门创建反应应用程序的建议解决方案:github.com/facebook/create-react-app/issues/7237
【解决方案2】:

正如 Speckles 所指出的(感谢您让我头疼),iOS 会在启动应用程序之前安装新的软件。所以软件没有机会捕捉到“安装”状态。

解决方法:检查注册是否处于等待状态,然后处理。

I've made an (untested) example of handling this. - 默认 CRA SW 的一个模组。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-03-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-08-13
    • 1970-01-01
    相关资源
    最近更新 更多