TWA 的 PostMessage

Sayed El-Abady
Sayed El-Abady

从 Chrome 115 开始,Trusted Web Activity (TWA) 可以使用 postMessage 发送消息。本文档将详细介绍在应用和网站之间进行通信所需的设置。

在本指南结束后,您将能够:

  • 了解客户端和网站内容验证的运作方式。
  • 了解如何初始化客户端与 Web 内容之间的通信渠道。
  • 了解如何向 Web 内容发送消息以及从 Web 内容接收消息。

若要按照本指南操作,您需要:

  • 将最新的 androidx.browser(最低 v1.6.0-alpha02)库添加到 build.gradle 文件中。
  • 对于 TWA,Chrome 版本 115.0.5790.13 或更高版本。

window.postMessage() 方法可安全地在 Window 对象之间实现跨源通信。例如,在网页与其生成的弹出式窗口之间,或在网页与其中嵌入的 iframe 之间。

通常,只有当不同网页的脚本来自同一来源、共用相同的协议、端口号和主机时,才允许它们相互访问(也称为同源政策)。window.postMessage() 方法提供了一种受控机制,可在不同来源之间安全通信。这对于实现聊天应用、协作工具等非常有用。例如,聊天应用可以使用 postMessage 在不同网站上的用户之间发送消息。在 Trusted Web Activity (TWA) 中使用 postMessage 可能有点棘手,本指南将详细介绍如何在 TWA 客户端中使用 postMessage 向网页发送和接收消息。

将应用添加到 Web 验证

postMessage API 允许两个有效源(源和目标源)相互通信。为了让 Android 应用能够向目标源发送消息,它需要声明自己等同于哪个源源头。为此,您可以使用 Digital Asset Links (DAL),在 assetlinks.json 文件中添加应用的软件包名称,并将关系设为 use_as_origin,以便其如下所示:

[{   "relation": ["delegate_permission/common.use_as_origin"],   "target" : { "namespace": "android_app", "package_name": "com.example.app", "sha256_cert_fingerprints": [""] } }] 

请注意,在与 TWA 关联的源站上进行设置时,必须为 MessageEvent.origin 字段提供源站,但 postMessage 可用于与不包含数字资产链接的其他网站进行通信。例如,如果您拥有 www.example.com,则必须通过 DAL 证明这一点,但您可以与任何其他网站(例如 www.wikipedia.org)进行通信。

将 PostMessageService 添加到清单中

如需接收 postMessage 通信,您需要设置该服务,方法是在 Android 清单中添加 PostMessageService

<service android:name="androidx.browser.customtabs.PostMessageService" android:exported="true"/> 

获取 CustomTabsSession 实例

将服务添加到清单后,使用 CustomTabsClient 类绑定服务。连接后,您可以使用提供的客户端创建新会话,如下所示。 CustomTabsSession 是用于处理 postMessage API 的核心类。以下代码展示了服务连接后,如何使用客户端创建新会话,此会话用于postMessage

private CustomTabsClient mClient; private CustomTabsSession mSession;  // We use this helper method to return the preferred package to use for // Custom Tabs. String packageName = CustomTabsClient.getPackageName(this, null);  // Binding the service to (packageName). CustomTabsClient.bindCustomTabsService(this, packageName, new CustomTabsServiceConnection() {  @Override  public void onCustomTabsServiceConnected(@NonNull ComponentName name,      @NonNull CustomTabsClient client) {    mClient = client;     // Note: validateRelationship requires warmup to have been called.    client.warmup(0L);     mSession = mClient.newSession(customTabsCallback);  }   @Override  public void onServiceDisconnected(ComponentName componentName) {    mClient = null;  } }); 

您现在可能想知道这个 customTabsCallback 实例是什么,对吗?我们将在下一部分中创建此类。

创建 CustomTabsCallback

CustomTabsCallback 是 CustomTabsClient 的回调类,用于获取与自定义标签页中的事件相关的消息。其中一个事件是 onPostMessage,当应用收到来自网络的消息时,系统会调用此事件。将回调添加到客户端,以初始化 postMessage 通道以开始通信,如以下代码所示。

private final String TAG = "TWA/CCT-PostMessageDemo";  // The origin the TWA is equivalent to, where the Digital Asset Links file // was created with the "use_as_origin" relationship. private Uri SOURCE_ORIGIN = Uri.parse("https://source-origin.example.com");  // The origin the TWA will communicate with. In most cases, SOURCE_ORIGIN and // TARGET_ORIGIN will be the same. private Uri TARGET_ORIGIN = Uri.parse("https://target-origin.example.com");  // It stores the validation result so you can check on it before requesting // postMessage channel, since without successful validation it is not possible // to use postMessage. boolean mValidated;  CustomTabsCallback customTabsCallback = new CustomTabsCallback() {      // Listens for the validation result, you can use this for any kind of     // logging purposes.     @Override     public void onRelationshipValidationResult(int relation, @NonNull Uri requestedOrigin,         boolean result, @Nullable Bundle extras) {         // If this fails:         // - Have you called warmup?         // - Have you set up Digital Asset Links correctly?         // - Double check what browser you're using.         Log.d(TAG, "Relationship result: " + result);         mValidated = result;     }      // Listens for any navigation happens, it waits until the navigation finishes     // then requests post message channel using     // CustomTabsSession#requestPostMessageChannel(sourceUri, targetUri, extrasBundle)      // The targetOrigin in requestPostMessageChannel means that you can be certain their messages are delivered only to the website you expect.     @Override     public void onNavigationEvent(int navigationEvent, @Nullable Bundle extras) {         if (navigationEvent != NAVIGATION_FINISHED) {             return;         }          if (!mValidated) {             Log.d(TAG, "Not starting PostMessage as validation didn't succeed.");         }          // If this fails:         // - Have you included PostMessageService in your AndroidManifest.xml ?         boolean result = mSession.requestPostMessageChannel(SOURCE_ORIGIN, TARGET_ORIGIN, new Bundle());         Log.d(TAG, "Requested Post Message Channel: " + result);     }      // This gets called when the channel we requested is ready for sending/receiving messages.     @Override     public void onMessageChannelReady(@Nullable Bundle extras) {         Log.d(TAG, "Message channel ready.");          int result = mSession.postMessage("First message", null);         Log.d(TAG, "postMessage returned: " + result);     }      // Listens for upcoming messages from Web.     @Override     public void onPostMessage(@NonNull String message, @Nullable Bundle extras) {         super.onPostMessage(message, extras);         // Handle the received message.     } }; 

通过网页进行通信

现在,我们可以通过托管应用发送和接收消息,那么如何通过网页执行相同的操作?通信必须从托管应用开始,然后网页需要从第一个消息中获取端口。此端口用于进行回传。您的 JavaScript 文件将如下所示:

window.addEventListener("message", function (event) {   // We are receiveing messages from any origin, you can check of the origin by   // using event.origin    // get the port then use it for communication.   var port = event.ports[0];   if (typeof port === 'undefined') return;    // Post message on this port.   port.postMessage("Test")    // Receive upcoming messages on this port.   port.onmessage = function(event) {     console.log("[PostMessage1] Got message" + event.data);   }; }); 

您可以在此处找到完整的示例

照片由 Joanna Kosinska 拍摄,选自 Unsplash