# WebAPK

> Android 上的 Chrome 如何由 manifest 铸造已签名的 APK，WebAPK 比快捷方式多出哪些包身份、intent filter 与启动画面，以及更新如何到达。

WebAPK 是用户在 Android 上安装 PWA 时由 Chrome 生成的一个很小的已签名 Android 包，让 web 应用拥有真正的包名、应用抽屉条目、设置 › 应用中的条目，以及针对其 scope 的 intent filter，而不只是一个主屏幕快捷方式。Chrome 57 引入了它；Samsung Internet 铸造自己的等价物；Firefox for Android 与 WebView 只安装快捷方式，桌面平台都不使用这一模型（兼容性数据集 `webapk`）。

## 工作原理

安装经由 Google 的铸造服务完成，这也是 WebAPK 在安装时需要网络的原因：

1. 用户从 `beforeinstallprompt` 的 `prompt()`、mini-infobar 或菜单接受安装。
2. Chrome 把 manifest URL、解析后的内容与图标哈希发送给 WebAPK 服务器。
3. 服务器返回一个用它自己的密钥签名的 APK，其中只有元数据：包名、标签、图标、启动画面，以及针对 manifest `scope` 的 intent filter。
4. Chrome 通过 Android 的包安装器安装它；Android 把它当作应用对待，因此它出现在应用抽屉与设置 › 应用中，从两处都可以卸载。

服务器不可达时，Chrome 回退为普通的主屏幕快捷方式；页面在安装时无法得知走的是哪条路径，只能事后判断（见实测细节）。

与快捷方式相比，APK 带来的是系统级身份：

| 行为 | WebAPK | 主屏幕快捷方式 |
|---|---|---|
| 包名、应用抽屉、设置 › 应用条目 | 有（`org.chromium.webapk.<hash>`） | 无；仅主屏幕 |
| 从其他应用打开 `scope` 内的链接 | 在 WebAPK 中打开（intent filter） | 在浏览器中打开 |
| 启动画面 | 由 `name`、`background_color` 与 512 px 图标生成 | 无 |
| 最近任务条目 | 带应用名与 `theme_color` 的独立任务 | 浏览器任务 |
| 重复安装 | 不可能；复用已有的包 | 可能；重复项共用一份存储 |
| 安装时需要网络 | 是 | 否 |

web 内容仍然是 Chrome：WebAPK 在一个 Chrome activity 中用用户的 Chrome 配置文件启动 `start_url`，所以 Cookie、存储、service worker 与 Chrome 版本都是浏览器的。web 平台之外没有任何原生 API 变得可用，上架 Play 则需要改用 Trusted Web Activity。

## manifest 更新

Chrome 在已安装应用启动时重新读取 manifest，当 `name`、`short_name`、`icons`、`start_url`、`scope`、`display`、`orientation`、`theme_color` 或 `background_color` 发生变化时向铸造服务器申请新的 APK；web.dev 的 WebAPKs 文章记载该检查被限制为大约一天一次，新 APK 在后台安装，因此改动会在之后的某次启动中显现。`start_url` 的源发生变化或 manifest 不再能解析时，更新会停止，而不是破坏已安装的应用。

## 示例

两个示例都运行在页面中；第一个展示 WebAPK 在任何页面 JavaScript 之前就会绘制的内容，第二个按安装类型分支。

### 决定启动画面与任务卡片的 manifest 字段

启动画面由三个成员合成，显示到首次绘制为止；`theme_color` 为状态栏与最近任务卡片上色。缺少 `background_color` 时启动画面为白色，小于 512 px 的图标会被放大。

```json
{
  "name": "Trail Notes",
  "short_name": "Trails",
  "start_url": "/?source=webapk",
  "scope": "/",
  "display": "standalone",
  "background_color": "#101820",
  "theme_color": "#101820",
  "icons": [
    { "src": "/icons/maskable-512.png", "sizes": "512x512", "type": "image/png", "purpose": "maskable" },
    { "src": "/icons/any-512.png", "sizes": "512x512", "type": "image/png" }
  ]
}
```

`start_url` 上的 `?source=webapk` 查询参数是在统计中区分已安装应用启动次数的惯用做法；它也让已安装应用的第一个请求在服务器日志中可以辨认，否则它与标签页的请求完全相同。

### 检测已安装上下文与快捷方式回退

`display-mode: standalone` 在 WebAPK、manifest 要求 `standalone` 的快捷方式，以及 TWA 中都为 `true`，所以它回答"是否已安装"，却回答不了"哪种安装"。referrer 可以分出 TWA；排除掉 WebAPK 独有的 intent 捕获之后，剩下的就是快捷方式。

```js
const standalone = matchMedia('(display-mode: standalone)').matches;
const fromTwa = document.referrer.startsWith('android-app://');
const launchedFromLink = new URL(location.href).searchParams.get('source') !== 'webapk' && standalone;

if (!standalone) {
  // 浏览器标签页：beforeinstallprompt 触发后可以显示安装界面。
} else if (fromTwa) {
  // Play 分发的 TWA：可能有 Digital Goods API。
} else if (launchedFromLink) {
  // 经 scope 的 intent filter 打开：只有 WebAPK 会这样。
} else {
  // 从图标启动：WebAPK 或快捷方式；行为相同。
}
```

把最后两个分支同等对待是稳妥的选择：以快捷方式安装的用户拥有同样的 web 能力，只是少了系统集成，页面中不应有任何东西对他们失效。

:::observed
在 Android 上用 Chrome（英文界面）安装一个 PWA 后，设置 › 应用以它的 manifest `name` 列出该应用，点进去显示的包名以 `org.chromium.webapk.` 开头，这正是 web.dev 的 WebAPKs 文章记载的铸造包前缀；Firefox 为同一站点创建的主屏幕快捷方式则不会出现在设置 › 应用的任何位置。从其他应用打开 manifest `scope` 内的链接会直接启动 WebAPK，并显示 `background_color` 颜色的启动画面，而不是打开一个 Chrome 标签页。
:::

## 另请参阅

- [WebAPKs on Android](https://web.dev/articles/webapks)（web.dev）
- [Installation](https://web.dev/learn/pwa/installation)（web.dev）
- [Trusted Web Activities overview](https://developer.chrome.com/docs/android/trusted-web-activity/overview)（developer.chrome.com）
- [WebAPK 浏览器支持](/zh/compatibility/webapk/)
- [Trusted Web Activity](/zh/reference/installation/twa/)
- [beforeinstallprompt 事件](/zh/reference/installation/install-prompt/)
- [N+1 安装问题](/zh/reference/installation/n-plus-one/)
- [Chrome for Android 上的 PWA](/zh/reference/platforms/chrome-android/)