# Chrome 与 Android 上的 PWA

> PWA 在 Android 版 Chrome 上如何安装：beforeinstallprompt 流程、GMS 设备上生成的 WebAPK、manifest 更新时机，以及用于上架 Play 的 Trusted Web Activity。

import CompatTable from '@components/CompatTable.astro';

在搭载 Google Mobile Services（GMS）的 Android 设备上，从 Chrome 安装 PWA 会生成一个 WebAPK：Chrome 向 Google 服务器请求并安装的一个小型 APK，使应用获得启动器入口、Android 应用设置中的条目，以及覆盖其 scope 的 intent filter。没有 GMS 时，Chrome 只会添加一个带浏览器角标的主屏幕快捷方式；要上架 Google Play，则需把同一个 Web 应用封装为 Trusted Web Activity（TWA）。

## 工作原理

安装形态取决于浏览器，而不是站点。在 Android 上，只有 GMS 设备上的 Chrome 与三星设备上的 Samsung Internet 会把 PWA 安装为 WebAPK；Firefox、Edge、Opera 以及无 GMS 设备上的 Chrome 添加的是在浏览器中打开站点的快捷方式（[Making PWAs installable](https://developer.mozilla.org/en-US/docs/Web/Progressive_web_apps/Guides/Making_PWAs_installable)，MDN）。Android 版 Chrome 能运行的每个 Android 版本都支持安装，最早到 Jelly Bean。

<CompatTable feature="webapk" />

Trusted Web Activity 是另一套机制，有自己的数据集：

<CompatTable feature="twa" />

### Chrome 推广安装的前提

Chromium 系浏览器（Chrome、Samsung Internet、Edge）只在 manifest 具备以下成员时才主动推广安装：`name` 或 `short_name`；包含 192px 与 512px 两项的 `icons`；`start_url`；`display` 或 `display_override`；`prefer_related_applications` 为 `false` 或不存在。页面必须通过 `https` 提供，开发阶段可用 `localhost` 或 `127.0.0.1`；`file://` 虽是安全上下文，但不算数。Service Worker 不是可安装性的必要条件。Android 版 Chrome、桌面版 Chrome、桌面版 Edge 与桌面版 Safari 还允许用户安装完全没有 manifest 的站点；manifest 的作用是让浏览器主动推广安装，并决定安装结果的形态。

Android 版 Chrome 面向用户的路径（英文界面）是三点 **More** 菜单，然后 **Install and create shortcut**，再 **Install**（[Chrome 帮助](https://support.google.com/chrome/answer/9658361?co=GENIE.Platform%3DAndroid)）。页面也可以在 `beforeinstallprompt` 处理函数中自行打开同一个对话框，代码见「示例」。

### WebAPK 带来什么

Chrome 根据 manifest 及其他元数据构建 WebAPK（[WebAPKs on Android](https://web.dev/articles/webapks)，web.dev）。安装之后：

- 应用在用户当初安装它的那个 Chrome 中打开，而不是 WebView。
- 它为 `scope` 内的每个 URL 注册 intent filter。设置 `"scope": "/app/"` 后，点击指向 `/app/read/book` 的链接会打开应用，`/help/` 则在浏览器标签页中打开。
- 权限不会在安装时授予。Android 会在安装时授予原生应用通知权限，但 WebAPK 必须在运行时通过 Chrome 自己的提示与设置申请。
- 存储就是浏览器 profile 的存储。Cookie、IndexedDB、Cache Storage 与 Service Worker 注册都与 Chrome 共享；清除 Chrome 的站点数据也会清掉已安装应用的数据。
- Chrome 71 及之后生成的 WebAPK 在 manifest 提供 512px 图标时，会在启动画面显示更大的图标。
- 生成的 APK 不被 Google Play 接受。要上架 Play 得用 TWA。

### manifest 变更之后

Android 版 Chrome 在启动时，如果距上次检查超过 24 小时，会安排一次 manifest 拉取；拉取失败后间隔可能延长到 30 天（[How Chrome handles updates to the web app manifest](https://web.dev/articles/manifest-updates)，web.dev）。`name`、`short_name`、`icons`、`background_color`、`display`、`orientation`、`scope`、`shortcuts`、`start_url`、`theme_color` 或 `web_share_target` 的变化会排队生成新的 WebAPK，Chrome 会等到所有应用窗口关闭、设备接通电源并连上 Wi-Fi 后才去请求。重命名或移动 manifest 文件可能让更新彻底停止；在 manifest 里存放用户专属值则会毫无收益地反复重建 APK。

### 面向 Google Play 的 Trusted Web Activity

TWA 通过基于 Custom Tabs 的协议，从你发布的 Android 应用中打开你的源，Chrome 72 及之后可用（[Trusted Web Activities](https://developer.chrome.com/docs/android/trusted-web-activity/overview)，developer.chrome.com）。应用与站点必须通过 Digital Asset Links 建立关联；关联成立时，内容在用户的浏览器中全屏渲染，没有浏览器 UI。宿主应用无法直接访问 Cookie 或 `localStorage`，状态只能通过 URL（查询参数与 intent URI）传递。在低于 72 的 Chrome 上，同一个应用会回退为带普通工具栏的 Custom Tab。Bubblewrap 是生成 Android 工程的 Node.js CLI；打包步骤见 [Trusted Web Activity（TWA）：PWA 进入 Play 商店](/zh/reference/installation/twa/)。

## 示例

两个示例在浏览器标签页、WebAPK 与桌面应用窗口中都能原样运行，由特性检测决定用户看到什么。

### 展示自己的安装按钮

一旦 Chrome 判定页面可安装，`beforeinstallprompt` 就会在 `window` 上触发，通常在页面加载期间，但没有保证的时机。按钮要一直隐藏到事件到来，因为该事件只有 Chromium 系浏览器实现：

<CompatTable feature="install-prompt" />

```html
<button id="install" hidden>安装</button>
```

```js
let installPrompt = null;
const installButton = document.querySelector('#install');

window.addEventListener('beforeinstallprompt', (event) => {
  event.preventDefault();          // 压制 Chrome 自带的安装 UI
  installPrompt = event;           // 保存事件供点击处理函数使用
  installButton.removeAttribute('hidden');
});

installButton.addEventListener('click', async () => {
  if (!installPrompt) return;
  const consumedPrompt = installPrompt;
  installButton.disabled = true;   // 对话框打开期间禁止再次点击
  try {
    const result = await consumedPrompt.prompt();
    console.log(`安装提示结果：${result.outcome}`); // "accepted" 或 "dismissed"
  } finally {
    installPrompt = null;          // 无论哪条退出路径，实例都已被消费
    installButton.disabled = false;
    installButton.setAttribute('hidden', '');
  }
});
```

`prompt()` 必须在用户激活的处理函数内运行，并且对同一个 `BeforeInstallPromptEvent` 实例只能调用一次（[BeforeInstallPromptEvent: prompt() method](https://developer.mozilla.org/en-US/docs/Web/API/BeforeInstallPromptEvent/prompt)，MDN），所以处理函数在 `finally` 中退役该事件，而不是只在成功后才这么做。iOS 上不存在这个事件，按钮会一直隐藏，用户从分享菜单安装。

### 读取实际生效的显示模式

`display-mode` 报告的是浏览器实际应用的模式，当浏览器不支持所请求的值时，它会与 manifest 中的 `display` 不同（[`display-mode`](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/display-mode)，MDN）。它描述的是呈现方式而非安装状态，因此只用它调整 UI，不要当成安装 API：

```js
function applyDisplayMode(installButton) {
  if (!('matchMedia' in window)) {
    // matchMedia 不可用：让按钮保持隐藏，依赖浏览器菜单。
    installButton.hidden = true;
    return;
  }
  if (window.matchMedia('(display-mode: standalone)').matches) {
    installButton.hidden = true; // 已经以应用窗口运行
  }
}
```

匹配到 `standalone` 只说明页面不在普通标签页中，并不证明存在 WebAPK，因为桌面应用窗口与 iOS 主屏幕 Web 应用也会匹配同一个媒体查询。

## 实测行为

WebAPK 的更新周期在设备上可以直接看到，这是区分「manifest 没更新」与「缓存问题」最快的办法。

:::observed
Android 版 Chrome 在 `about://webapks`（英文界面）列出已安装的 WebAPK。每一项都有一个 **Update** 按钮可强制检查 manifest；manifest 变更后，页面上显示的更新状态先是「Pending」，通常几分钟内切换为「Successful」，见 web.dev 的 manifest-updates 文章（2024-09-19 更新）。到了这一步仍显示旧图标的 WebAPK，是缓存问题而不是更新问题。
:::

卸载走的是 Android 而不是 Chrome：**Settings** > **Apps** > **See all apps**，点开该 Web 应用，再点 **Uninstall**（英文界面，Chrome 帮助）。开发者修改应用 `name` 后，Chrome 会向用户展示新名称，并提供 **OK** 接受或 **Uninstall app** 卸载；Chrome 帮助把模仿其他应用的改名视为开发者可能存在恶意的信号。

## 另请参阅

- [WebAPKs on Android](https://web.dev/articles/webapks)（web.dev）
- [How Chrome handles updates to the web app manifest](https://web.dev/articles/manifest-updates)（web.dev）
- [Trusted Web Activities](https://developer.chrome.com/docs/android/trusted-web-activity/overview)（developer.chrome.com）
- [可安装性条件：PWA 如何才能被安装](/zh/reference/installation/installability-criteria/)
- [WebAPK：Chrome 在 Android 上安装 PWA 的方式](/zh/reference/installation/webapk/)
- [Trusted Web Activity（TWA）：PWA 进入 Play 商店](/zh/reference/installation/twa/)
- [安装提示：beforeinstallprompt 与自定义安装](/zh/reference/installation/install-prompt/)
- [三星 Internet 浏览器上的 PWA](/zh/reference/platforms/samsung-internet/)
- [国内厂商浏览器](/zh/compatibility/cn-vendor-browsers/)