# N+1 安装问题

> 已安装的 web 应用为何归属于安装它的浏览器，同一站点为何会在一台设备上存在多份数据隔离的安装，哪些平台会去重，页面能检测到什么。

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 上复现。

:::observed
在装有 Chrome 与 Edge 的 Windows 11（英文界面）上，从两个浏览器分别安装同一站点，设置 › Apps › Installed apps 中会出现两个名称相同的条目，只有发布者一栏分别显示 "Google Chrome" 与 "Microsoft Edge"，两个独立的应用窗口不共享登录状态；`chrome://apps` 只列出 Chrome 的副本，`edge://apps` 只列出 Edge 的副本。在运行 iOS 16.4 或更高版本的 iPhone（英文界面）上，从共享菜单用不同名称两次添加同一站点，会得到两个主屏幕图标，各自独立要求登录，与 WebKit 对主屏幕 web 应用按安装隔离存储的描述一致。
:::

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

## 另请参阅

- [Installing and uninstalling web apps](https://developer.mozilla.org/en-US/docs/Web/Progressive_web_apps/Guides/Installing)（developer.mozilla.org）
- [Web Push for Web Apps on iOS and iPadOS](https://webkit.org/blog/13878/web-push-for-web-apps-on-ios-and-ipados/)（webkit.org）
- [Web Application Manifest: id member](https://www.w3.org/TR/appmanifest/#id-member)（w3.org）
- [`id` manifest 成员](/zh/reference/manifest/id/)
- [getInstalledRelatedApps()](/zh/reference/installation/get-installed-related-apps/)
- [卸载已安装的 PWA](/zh/reference/installation/uninstall-retention/)
- [iOS 添加到主屏幕](/zh/reference/installation/ios-add-to-home-screen/)