跳转到内容

安装 · 概念

N+1 安装问题

发布于

N+1 安装问题是 web 应用安装由浏览器而非操作系统执行所带来的结果:每一个安装过该站点的浏览器都生成自己的应用,拥有自己的存储与自己对已安装状态的记录,所以一台设备可以持有 N 个浏览器的副本,再加上用户下一个尝试的浏览器所装的那一份。Chromium 浏览器(Chrome 73、Edge 79)、iOS 16.4 与 macOS 14 上的 Safari、Windows 上的 Firefox 143 都以这种方式各自拥有安装(MDN “Installing and uninstalling web apps”、web.dev “Installation”)。

已安装的 web 应用运行在创建它的那个浏览器里。Chrome 的桌面应用窗口是一个读取 Chrome 配置文件存储的 Chrome 进程;Edge 的则是读取 Edge 存储的 Edge 进程;iOS 上 Safari 的主屏幕 web 应用使用与 Safari 标签页分离的独立数据存储。由此产生三个后果,MDN 与 web.dev 都有记载:

  1. 已安装状态只有安装它的浏览器知道。 Edge 知道站点已从 Edge 安装,再次访问时提供 “Open”;Chrome 访问同一站点时提供 “Install”,因为它没有 Edge 那份应用的记录。再从 Chrome 安装一次,设备上就有两个应用。
  2. 存储按安装隔离。 Cache Storage、IndexedDB、Cookie 与 localStorage 都存放在安装方浏览器的配置文件中(iOS 上则在 web 应用自己的容器里),所以在 Edge 副本中登录的用户,在 Chrome 副本中是未登录状态,一方缓存的离线数据在另一方也不存在。
  3. 卸载按安装进行。 从 edge://apps 移除 Edge 副本,chrome://apps 中的 Chrome 副本仍在,反之亦然。

在同一浏览器内,行为取决于平台:

平台 同一浏览器再次安装同一站点 身份键
Chrome 与 Edge,桌面端(Windows、macOS、Linux、ChromeOS) 拒绝;安装入口消失,浏览器改为提供打开已有应用 manifest id,缺省回退到 start_url
Chrome,Android(WebAPK) 拒绝;每个浏览器每个 manifest id 只有一个 WebAPK manifest id
Chrome,Android(WebAPK 服务器不可达时的快捷方式回退) 允许;每个快捷方式指向同一份浏览器存储 仅快捷方式
Safari,iOS 与 iPadOS 有意允许,用用户输入的名称区分副本;每份副本存储隔离 manifest id 加添加时输入的名称
Safari,macOS 14+ 允许;再次 “Add to Dock” 会创建第二个 web 应用 添加时输入的名称

manifest 的 id 成员让浏览器在 start_url 变化后仍能认出已安装过的站点;没有它,身份就是 start_url,改动该 URL 会让 Chromium 把一次更新当成一个新应用。在 iOS 上,同一 id 刻意不作为去重键:WebKit 把多份安装(工作账号与个人账号)记载为一项特性,并用 id 与用户为应用起的名字一起,让每份副本的专注模式与通知设置相互独立。

页面无法查询其他浏览器的安装。getInstalledRelatedApps() 只在 manifest 把该 PWA 列入 related_applications、且在同一浏览器中时才报告它已安装;display-mode: standalone 只说明当前这一份正以安装态运行。因此站点要做的选择是:让重复安装变得廉价(无状态页面、登录后在任何副本上恢复数据),还是用明确的文案引导用户只用一个浏览器,并接受这种引导本身无法强制执行。

以下行为可以在装有两个 Chromium 浏览器的同一台机器和一部 iPhone 上复现。

在 iOS 上不改名称重新添加站点同样会生成第二个图标,系统不会合并它们。相反,在 Chrome 桌面端访问已安装的站点时,地址栏不再显示安装图标,三点菜单项从 “Install [应用名]…” 变为 “Open in [应用名]”。

规范

规范状态
无。