# 通过应用商店分发 PWA

> 用 Trusted Web Activity 把 PWA 封装上架 Google Play，用 PWA Builder 打包提交微软商店，并对已安装的用户隐藏安装横幅。

import { Steps } from '@astrojs/starlight/components';

完成本指南后，同一个站点会以 Trusted Web Activity（TWA）上架 Google Play，以打包 PWA 上架微软商店，两者都由线上 manifest 生成而不是重写，而且网页版不再向已经装了商店版本的用户推销安装。TWA 在用户的浏览器中全屏渲染你的来源，所以 Web 体验就是产品本身；商店包只是一个外壳加一条经过验证的站点链接。

你需要一个部署在自有域名上的可安装 PWA、运行 Bubblewrap CLI 所需的 Node.js 14.15.0 及以上（缺少 JDK 17 与 Android 命令行工具时首次运行会自动下载）、一个 Google Play 开发者账号，以及一个已加入 Windows Developer Program 的个人 Microsoft 账号。

## 用 Bubblewrap 生成 Android 项目

Trusted Web Activity 在 Android 版 Chrome 72 及以上可用；Chrome 不支持 TWA 时会回退为带工具栏的 Custom Tab。Bubblewrap 读取 manifest，请你逐项确认要复制进 Android 项目的取值，然后构建出已签名的 APK：

<Steps>

1. 安装 CLI（不要用 `sudo`），并基于 manifest URL 初始化。

   ```bash
   npm i -g @bubblewrap/cli
   bubblewrap init --manifest=https://notes.example/manifest.webmanifest
   ```

   `init` 会在交互过程中创建签名密钥；保管好 keystore 及其密码，它之后会成为上传密钥。

2. 构建并安装到已连接的设备上。

   ```bash
   bubblewrap build
   bubblewrap install   # 或：adb install app-release-signed.apk
   ```

   构建产物是 `app-release-signed.apk`。首次启动时站点会以带地址栏的 Custom Tab 打开；在 Digital Asset Links 验证通过之前这是预期行为。

3. 确认是哪个浏览器在提供该 activity。

   ```bash
   adb logcat -v brief | grep -e TWAProviderPicker
   ```

</Steps>

构建失败时运行 `bubblewrap doctor`：它会检查 `~/.bubblewrap/config.json` 中记录的 JDK 与 Android SDK 是否存在且版本正确。

## 发布 Digital Asset Links 声明

只有站点为应用作保，地址栏才会消失。把下面的文件放在 `https://notes.example/.well-known/assetlinks.json`；Android 的 App Links 文档要求 `Content-Type: application/json`、HTTPS，且不能有 301 或 302 重定向：

```json
[{
  "relation": ["delegate_permission/common.handle_all_urls"],
  "target": {
    "namespace": "android_app",
    "package_name": "example.notes.twa",
    "sha256_cert_fingerprints": [
      "AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99:AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99"
    ]
  }
}]
```

指纹必须是给用户实际收到的 APK 签名的那把密钥的证书。Google Play 要求 AAB 上传使用 Play App Signing，即 Google 用一把不同于 Bubblewrap 上传密钥的「应用签名密钥」重新签名；Android 文档指出在这种情况下，用本地 keystore 跑 `keytool` 得到的指纹 "will usually not match the one on users' devices"。到 Play Console 的 **Release** > **Setup** > **App integrity** 复制 "App signing key certificate" 的 SHA-256，并把上传密钥与应用签名密钥两枚指纹都列进去，让测试安装与 Play 安装都能通过验证。指纹不匹配或缺失会悄悄把 TWA 降级为顶部带浏览器界面的 Custom Tab。

## 用 PWA Builder 打包微软商店版本

微软的文档写明不需要改代码：在 Partner Center 预留名称、用 PWA Builder 打包、提交。在 Partner Center 选择 **Apps and games** > **New product** > **MSIX or PWA app**，输入名称后选 **Reserve product name**，再打开 **Product management** > **Product Identity**，复制 **Package ID**、**Publisher ID** 与 **Publisher display name**。到 pwabuilder.com 在 **Ship your PWA to app stores** 下输入站点 URL，点 **Start**，先处理 **Action Items** 列出的问题，然后 **Package For Stores** > **Windows** > **Generate Package**，粘贴那三个值，点 **Download Package**。zip 里有一个 `.msixbundle` 和一个 `.classic.appxbundle`；在 **Start your submission** 的 **Packages** 步骤把两者都传上去。微软表示审核通常在 24 到 48 小时内完成。前端与 Service Worker 的改动无需重新提交即可到达商店用户；manifest 的改动（图标、名称、`file_handlers`、`share_target`）则需要新包，因为 manifest 会被复制进包内。

## 商店应用已存在时隐藏安装横幅

`navigator.getInstalledRelatedApps()` 解析为用户已安装的相关应用，也是 MDN 点名用于移除「安装我们的 App」横幅的 API。它要求顶层安全上下文（在 `<iframe>` 中调用会抛出 `InvalidStateError`），且不是 Baseline，所以方法缺失时默认显示横幅：

```js
async function shouldShowInstallBanner() {
  if (!('getInstalledRelatedApps' in navigator)) return true;
  const related = await navigator.getInstalledRelatedApps();
  return related.length === 0;
}
```

该调用只能找到在 manifest 的 `related_applications` 中声明、且对方也反向指回的应用：Android 应用通过上面的 Digital Asset Links 文件，Windows 应用通过 URI handlers，PWA 通过一条指向自身的 `related_applications` 条目。

```json
{
  "related_applications": [
    { "platform": "play", "id": "example.notes.twa", "url": "https://play.google.com/store/apps/details?id=example.notes.twa" },
    { "platform": "windows", "id": "12345Publisher.FieldNotes_abcdef123456" }
  ]
}
```

`prefer_related_applications` 保持缺省或 `false`；在 Android 上设为 `true` 会把用户送去 Google Play 而不是安装 Web 应用。

:::observed
微软的商店发布页（learn.microsoft.com，2026-09-02 更新）记载，Edge 会给从微软商店安装的 PWA 的首次导航加上 `Referer: app-info://platform/microsoft-store`，客户端可通过 `document.referrer` 读取。在起始 URL 上记录这个值，就能把商店安装与浏览器安装分开统计，而不需要第二个构建。
:::

## 另请参阅

- [Trusted Web Activity](/zh/reference/installation/twa/)
- [`related_applications`](/zh/reference/manifest/related-applications/)
- [`getInstalledRelatedApps()`](/zh/reference/installation/get-installed-related-apps/)
- [商店政策](/zh/ecosystem/stores/)
- [Trusted Web Activities quick start guide](https://developer.chrome.com/docs/android/trusted-web-activity/quick-start)（developer.chrome.com）
- [Publish a PWA to the Microsoft Store](https://learn.microsoft.com/en-us/microsoft-edge/progressive-web-apps/how-to/microsoft-store)（learn.microsoft.com）

← 返回[指南](/zh/guides/)总览。