Web In Play 的新动态

自去年推出 Trusted Web Activity 以来,Chrome 团队一直在持续改进该产品,让 Bubblewrap 更易于使用,添加了即将推出的 Google Play 结算服务集成等新功能,并使其可在更多平台(如 ChromeOS)上使用。本文将总结 Trusted Web Activity 的最新和即将推出的更新。

全新的对话泡和“可信网络活动”功能

气泡封装可帮助您创建可在 Trusted Web Activity 内启动 PWA 的应用,而无需了解平台专用工具。

简化的设置流程

以前,使用 Bubblewrap 需要手动设置 Java Development Kit 和 Android SDK,两者都容易出错。该工具现在会在首次运行时自动下载外部依赖项。

如果您愿意,仍然可以选择使用现有安装的依赖项,新的 doctor 命令有助于发现问题并建议对配置进行修复,现在可以使用 updateConfig 命令从命令行更新这些配置。

改进的向导

使用 init 创建项目时,Bubblewrap 需要获取信息来生成 Android 应用。该工具会从 Web 应用清单中提取值,并尽可能提供默认值。

您可以在创建新项目时更改这些值,但以前每个字段的含义并不明确。初始化对话框已重新构建,为每个输入字段提供了更好的说明和验证。

display:支持全屏和屏幕方向

在某些情况下,您可能希望应用尽可能多地使用屏幕;在构建 PWA 时,可通过将 Web 应用清单中的 display 字段设置为 fullscreen 来实现这一点。

当 Bubblewrap 在 Web 应用清单中检测到全屏选项时,它会将 Android 应用配置为在 Android 专用术语中也以全屏模式或沉浸模式启动。

Web 应用清单中的 orientation 字段定义了应用应以纵向模式、横向模式还是设备当前使用的屏幕方向启动。现在,Bubblewrap 会读取 Web 应用清单字段,并在创建 Android 应用时将其用作默认字段。

您可以在 bubblewrap init 流程中自定义这两种配置。

AppBundle 输出

app bundle 是一种发布格式,适用于将最终 APK 生成工作委托给 Play 并由其登录的应用。实际上,这样当从商店下载应用时,可以向用户提供较小的文件。

现在,Bubblewrap 会将应用打包为 App Bundle 文件并位于名为 app-release-bundle.aab 的文件中。在将应用发布到 Play 商店时,您应首选此格式,因为从 2021 年下半年开始,Play 商店会要求使用此格式

地理定位委托

用户希望其设备上安装的应用无论采用何种技术,行为均保持一致。在 Trusted Web Activity 中使用时,现在可以将 GeoLocation 权限委派给操作系统。启用后,用户会看到与使用 Kotlin 或 Java 构建的应用相同的对话框,并在同一位置找到用于管理权限的控件。

您可以通过 Bubblewrap 添加该功能,由于它会向 Android 项目添加额外的依赖项,因此您应仅在 Web 应用使用地理定位权限时启用该功能。

经过优化的二进制文件

存储空间有限的设备在全球某些地区很常见,这些设备的所有者通常更喜欢较小的应用。使用 Trusted Web Activity 的应用会生成小型二进制文件,从而消除这些用户的焦虑。

通过减少所需 Android 库的列表,优化了 Bubblewrap,使生成的二进制文件缩减了 80 万。实际上,这还不到先前版本生成的平均大小的一半。若要利用更小的二进制文件,您只需使用最新版本的 Bubblewrap 更新应用即可。

如何更新现有应用

Bubblewrap 生成的应用由一个 Web 应用和用于打开 PWA 的轻量级 Android 封装容器组成。虽然在可信网络 activity 中打开的 PWA 遵循与任何 Web 应用相同的更新周期,但可以并且应该更新原生封装容器。

您应更新应用,以确保其使用的是最新版本的封装容器,并且提供最新的 bug 修复和功能。安装最新版本的 Bubblewrap 后,update 命令会将最新版本的封装容器应用于现有项目:

npm update -g @bubblewrap/cli
bubblewrap update
bubblewrap build

更新这些应用的另一个原因是确保将对网络清单的更改应用于应用。为此,请使用新的 merge 命令:

bubblewrap merge
bubblewrap update
bubblewrap build

质量标准更新

Chrome 86 对“可信网络活动质量标准”进行了变更。如需了解详细说明,请参阅使用 Trusted Web Activity 的 PWA 质量标准变更

简而言之,您应该确保应用能够处理以下情景,以防止应用崩溃:

  • 未能在应用启动时验证数字资产链接
  • 未能针对离线网络资源请求返回 HTTP 200
  • 在应用中返回 HTTP 404 或 5xx 错误。

除了确保应用通过 Digital Asset Links 验证外,其余场景可由 Service Worker 处理:

self.addEventListener('fetch', event => {
  event.respondWith((async () => {
    try {
      return await fetchAndHandleError(event.request);
    } catch {
      // Failed to load from the network. User is offline or the response
      // has a status code that triggers the Quality Criteria.
      // Try loading from cache.
      const cachedResponse = await caches.match(event.request);
      if (cachedResponse) {
        return cachedResponse;
      }
      // Response was not found on the cache. Send the error / offline
      // page. OFFLINE_PAGE should be pre-cached when the service worker
      // is activated.
      return await caches.match(OFFLINE_PAGE);
    }
  })());
});

async function fetchAndHandleError(request) {
  const cache = await caches.open(RUNTIME_CACHE);
  const response = await fetch(request);

  // Throw an error if the response returns one of the status
  // that trigger the Quality Criteria.
  if (response.status === 404 ||
      response.status >= 500 && response.status < 600) {
    throw new Error(`Server responded with status: ${response.status}`);
  }

  // Cache the response if the request is successful.
  cache.put(request, response.clone());
  return response;
}

Workbox 遵循最佳实践,并在使用 Service Worker 时移除了样板。 或者,您也可以考虑使用 Workbox 插件来处理以下场景:

export class FallbackOnErrorPlugin {
  constructor(offlineFallbackUrl, notFoundFallbackUrl, serverErrorFallbackUrl) {
    this.notFoundFallbackUrl = notFoundFallbackUrl;
    this.offlineFallbackUrl = offlineFallbackUrl;
    this.serverErrorFallbackUrl = serverErrorFallbackUrl;
  }

  checkTrustedWebActivityCrash(response) {
    if (response.status === 404 || response.status >= 500 && response.status <= 600) {
      const type = response.status === 404 ? 'E_NOT_FOUND' : 'E_SERVER_ERROR';
      const error = new Error(`Invalid response status (${response.status})`);
      error.type = type;
      throw error;
    }
  }

  // This is called whenever there's a network response,
  // but we want special behavior for 404 and 5**.
  fetchDidSucceed({response}) {
    // Cause a crash if this is a Trusted Web Activity crash.
    this.checkTrustedWebActivityCrash(response);

    // If it's a good response, it can be used as-is.
    return response;
  }

  // This callback is new in Workbox v6, and is triggered whenever
  // an error (including a NetworkError) is thrown when a handler runs.
  handlerDidError(details) {
    let fallbackURL;
    switch (details.error.details.error.type) {
      case 'E_NOT_FOUND': fallbackURL = this.notFoundFallbackUrl; break;
      case 'E_SERVER_ERROR': fallbackURL = this.serverErrorFallbackUrl; break;
      default: fallbackURL = this.offlineFallbackUrl;
    }

    return caches.match(fallbackURL, {
      // Use ignoreSearch as a shortcut to work with precached URLs
      // that have _WB_REVISION parameters.
      ignoreSearch: true,
    });
  }
}

Google Play 结算服务

除了让您的应用在 Play 商店中销售数字商品和订阅之外,Google Play 结算服务还提供用于管理目录、价格和订阅的工具、实用的报告以及由用户已经熟悉的 Play 商店提供支持的结账流程。对于在 Play 商店中发布且销售数字商品的应用,这也是要求。

Chrome 88 将在 Android 上推出源试用,以便集成 Trusted Web ActivityPayment Request APIDigital Goods API,以通过 Google Play 结算服务实现购买流程。我们预计,此源试用也将适用于 ChromeOS 89 版。

重要提示:Google Play Billing API 有自己的术语,其中包括客户端和后端组件。本部分仅涵盖 API 的一小部分专门用于使用 Digital Goods API 和 Trusted Web Activity。在将 Google Play 结算服务集成到正式版应用之前,请务必阅读 Google Play 结算服务文档并了解其概念。

基本流程

Play 管理中心菜单

如需通过 Play 商店提供数字商品,您需要在 Play 商店中配置目录,并通过 PWA 将 Play 商店关联为付款方式。

准备好配置目录后,请先在 Play 管理中心的左侧菜单中找到“商品”部分:

在这里,您可以找到查看现有应用内商品和订阅的选项,以及用于添加新商品和订阅的“创建”按钮。

应用内商品

产品详情

如要创建新的应用内商品,您需要提供商品 ID、名称、说明和价格。请务必创建有意义且简单易记的商品 ID,您稍后需要用到这些 ID,并且 ID 一经创建便无法更改。

创建订阅时,您还必须指定结算周期。您可以选择列出订阅权益并添加相关功能,例如是否提供免费试订、初次体验价、宽限期和重新订阅选项。

创建每个商品后,请激活这些商品,从而在您的应用中提供这些商品。

如果您愿意,可以通过 Play Developers API 添加商品。

配置目录后,下一步是从 PWA 配置结账流程。为此,您可以结合使用 Digital Goods APIPayment Request API

使用 Digital Goods API 提取商品价格

使用 Google Play 结算服务时,您需要确保向用户显示的价格与商品详情中的价格一致。手动使这些价格保持同步是不可能的,因此,Web 应用可以使用 Digital Goods API 向底层付款服务提供商查询价格:

// The SKU for the product, as defined in the Play Store interface
async function populatePrice(sku) {
  try {
    // Check if the Digital Goods API is supported by the browser.
    if (window.getDigitalGoodsService) {
      // The Digital Goods API can be supported by other Payments provider.
      // In this case, we're retrieving the Google Play Billing provider.
      const service =
          await window.getDigitalGoodsService("https://play.google.com/billing");

      // Fetch product details using the `getDetails()` method.
      const details = await service.getDetails([sku]);

      if (details.length === 0) {
        console.log(`Could not get SKU: "${sku}".`);
        return false;
      }

      // The details will contain both the price and the currenncy.
      item = details[0];
      const value = item.price.value;
      const currency = item.price.currency;

      const formattedPrice = new Intl.NumberFormat(navigator.language, {
        style: 'currency', currency: currency }).format(value);

      // Display the price to the user.
      document.getElementById("price").innerHTML = formattedPrice;
    } else {
      console.error("Could not get price for SKU \"" + sku + "\".");
    }
  } catch (error) {
    console.log(error);
  }
  return false;
}

您可以通过检查 window 对象中是否有 getDigitalGoodsService() 来检测对 Digital Goods API 的支持。

然后,使用 Google Play 结算服务标识符作为参数调用 window.getDigitalGoodsService()。这将返回一个 Google Play 结算服务的服务实例,其他供应商可以实现对 Digital Goods API 的支持,并且将具有不同的标识符。

最后,对 Google Play 结算服务对象的引用调用 getDetails(),以参数形式传递相应商品的 SKU。该方法将返回详情对象,其中包含可向用户显示的商品的价格和货币。

开始购买流程

Payment Request API 支持网络上的购买流程,还可用于 Google Play 结算服务集成。如果您刚开始接触 Payment Request API,请参阅 Payment Request API 的工作原理了解详情。

为了将该 API 与 Google Play 结算服务搭配使用,您需要添加一种付款方式,该付款方式有一个名为 https://play.google.com/billing 的受支持付款方式,并将 SKU 作为该付款方式数据的一部分添加:

const supportedInstruments = [{
  supportedMethods: "https://play.google.com/billing",
  data: {
    sku: sku
  }
}];

然后,照常构建 PaymentRequest 对象并照常使用 API

const request = new PaymentRequest(supportedInstruments, details);

确认购买交易

交易完成后,您需要使用 Digital Goods API 确认付款。PaymentRequest 中的响应对象将包含用于确认交易的令牌:

const response = await request.show();
const token = response.details.token;
const service =
          await window.getDigitalGoodsService("https://play.google.com/billing");
await service.acknowledge(token, 'onetime');

Digital Goods API 和 Payment Request API 不知道用户身份。因此,您需要在后端将购买交易与用户关联,并确保他们有权访问已购商品。将购买交易与用户关联时,请记得保存购买令牌,因为您可能需要用它来验证购买交易是否已取消或已退款,或者订阅是否仍处于活跃状态。请查看 Real Time Developer Notifications APIGoogle Play Developer API,因为这两个 API 提供了用于在后端处理这些情况的端点。

检查现有使用权

用户可能已兑换促销代码或已订阅您的商品。为了验证用户是否具有适当的使用权,您可以对数字商品服务调用 listPurchases() 命令。这将返回客户在您的应用中进行的所有购买交易。也可在此处确认任何未确认的购买交易,以确保用户正确兑换其权限。

const purchases = await itemService.listPurchases();
for (p of purchases) {
  if (!p.acknowledged) {
    await itemService.acknowledge(p.purchaseToken, 'onetime');
  }
}

上传到 ChromeOS Play 商店

自 Chrome 85 版起,ChromeOS Play 商店也提供 Trusted Web Activity。在商店中列出应用的过程与在 ChromeOS 中和在 Android 中相同。

创建 app bundle 后,Play 管理中心会引导您完成在 Play 商店中列出该应用所需的步骤。在 Play 管理中心文档中,您可以找到创建应用详情、管理 APK 文件和其他设置方面的帮助,以及关于测试和安全发布应用的说明。

如需将应用限制为仅适用于 Chromebook,请在 Bubblewrap 中初始化应用时添加 --chromeosonly 标志:

bubblewrap init --manifest="https://example.com/manifest.json" --chromeosonly

在不使用 Bubblewrap 的情况下手动构建应用时,请向 Android 清单添加 uses-feature 标记:

<uses-feature  android:name="org.chromium.arc" android:required="true"/>

如果您的商品详情是与 Android 应用分享的,则仅限 ChromeOS 的软件包版本必须始终高于 Android 应用软件包的版本。您可以将 ChromeOS 软件包的版本设置为高于 Android 版本的版本号,这样您就不必针对每个版本更新这两个版本。