PostMessage API能否用于与Android WebView进行通信?

36

我通常使用HTML5的PostMessage API将信息从我的嵌入式内容传递到父级框架。最近,我的内容被用在了Android WebView中(据我所知,这是与iframe等效的本地Android应用程序)。是否有一种方法让本地应用程序监听我发送给他们的PostMessage事件呢?

我知道存在addJavascriptInterface,但我希望能够重用我现有的PostMessage代码而不编写新代码。


你解决过这个问题吗? - sirvon
不,我从来没有这样做过,抱歉。如果你发现了什么,请随意添加一个答案。 - Brian Putnam
5个回答

14

我知道这个问题很旧,但我遇到了它,所以我想在这里回答一下。简而言之——我发现postMessage至少可以用于从子iframe与父窗口进行通信,但是......

结果我们发现Android的WebView中iframe的行为并不理想,因此我们直接渲染了iframe的内容(如你所建议的)。这给我们留下了两个问题——首先,我们有很多来自iframe的消息钩子与其父级之间的通信,其次,我们仍然需要调用Android来对这些事件做出反应。

以下是我们代码中的一个示例消息——它分散在整个iframe中:

    parent.postMessage(JSON.stringify({
        action    : 'openModal',
        source    : embedId
    }), '*');
当我们在Android上时,我们想要使用Android对JavaScript接口的支持,在WebView中运行时注入一个对象来处理此请求。android's support for javascript interfaces Android端将如下所示:
class JsObject {
   @JavascriptInterface
    public boolean postMessage(String json, String transferList) {
        return false; // here we return true if we handled the post.
    }
}

// And when initializing the webview... 
webView.addJavascriptInterface(new JsObject(), "totDevice");
现在在 WebView 中运行时,totDevice 存在,而在 iframe 中运行时则不存在。因此,我们现在可以创建一个包装器来检查这个条件,并干净地在两种方法之间切换,而不是直接调用 parent.postMessage。在这里,我们还在我们的 Android 实现中添加了一个布尔开关,以防您只想处理其中一些消息:
function postMessage(parent, json, transferlist) {
    if (!totDevice || !totDevice.postMessage(json, transferList)) {
        parent.postMessage(json, transferlist);
    }
}

我们上面的原始 postMessage 可以被重写为:

    postMessage(parent, JSON.stringify({
        action    : 'openModal',
        source    : embedId
    }), '*');
现在我们有一套可以在iframe或Android WebView中运行而不需要更改的单一代码集(至少对于这部分代码而言)。
希望能对某些人有所帮助。

11
这里的答案在发布时是好的,但现在已经有androidx.webkit可用,我认为这是推荐的方法。
特别是,WebViewCompat.addWebMessageListenerWebViewCompat.postWebMessage将是javascript PostMessage API的对应物。
以下是从文档中复制的代码示例:
 // Web page (in JavaScript)
 myObject.onmessage = function(event) {
   // prints "Got it!" when we receive the app's response.
   console.log(event.data);
 }
 myObject.postMessage("I'm ready!");
 

 // App (in Java)
 WebMessageListener myListener = new WebMessageListener() {
   @Override
   public void onPostMessage(WebView view, WebMessageCompat message, Uri sourceOrigin,
            boolean isMainFrame, JavaScriptReplyProxy replyProxy) {
     // do something about view, message, sourceOrigin and isMainFrame.
     replyProxy.postMessage("Got it!");
   }
 };
 if (WebViewFeature.isFeatureSupported(WebViewFeature.WEB_MESSAGE_LISTENER)) {
   WebViewCompat.addWebMessageListener(webView, "myObject", rules, myListener);
 }

你是否了解isFeatureSupported部分的更多信息?这完全取决于System WebView APK,但是哪个版本呢?有没有办法要求特定版本的APK?我主要关注addWebMessageListenerallowedOriginRules,但如果没有支持,就不能让本地接口失败。 - themoondothshine
2
你可以做的是,如果该功能不受支持,你可以提供一个链接到Play商店条目,并要求用户更新。 - Maurice Lam

6
我遇到了类似的问题,我想监听在 webview 中发生的 .postMessage 事件。我在原始 HTML 中检查后发现该事件是这样触发的: window.parent.postMessage(JSON.stringify(message), '*'); 因此,我深入研究了 postMessage,并找到了 this link 来添加 eventListener。
window.addEventListener("message", receiveMessage, false);

function receiveMessage(event)
{
  if (event.origin !== "http://example.org:8080")
    return;

  // ...
}

Durban答案帮助我设置了Android JS接口。

首先,需要一个类似于这样的自定义类用于JavascriptInterface:

class JsObject {
    @JavascriptInterface
    public void receiveMessage(String data) {
        Log.i("JsObject", "postMessage data="+data);
        //handle data here
    }
}

在加载url/html之前向webview添加javascriptInterface。
webView.addJavascriptInterface(new JsObject(), "Android"); //"Android" is just a name

然后在 WebViewClientonPageStarted 回调中调用以下 JavaScript。
  @Override
  public void onPageStarted(WebView view, String url, Bitmap favicon) {
        webView.loadUrl("javascript:(function() {" +
                            "function receiveMessage(event) {\n" +
                            "Android.receiveMessage(JSON.stringify(event.data));\n" +
                            "}" +
                            "window.addEventListener(\"message\", receiveMessage, false);"+
                            "})()"
        );
        Log.i(TAG, "onPageStarted "+url);
  }

6

最近我们需要开发一个项目,需要将我们的原生Android应用与第三方提供的外部webview集成进行通信。

如果你无法接触那个webview的JS代码,那么这个问题在stackoverflow上引起了很大的兴趣。

我将描述我们所做的事情,以便通过JS PostMessage API消息步骤实现webview和本机应用程序之间的通信。

使用我们的webview实现。 我们实现了onPageFinished方法,以便注入我们的JS代码来加载Web。

override fun onPageFinished(url: String?) {
webview.loadUrl(
"javascript:(function() {" +
    "window.parent.addEventListener ('message', function(event) {" +
    " Android.receiveMessage(JSON.stringify(event.data));});" +
    "})()"
)
}

基本上我们正在创建一个监听器,将这些消息发送到我们自己的JS和Android Bridge interface。我们之前在Android活动中的webview设置中创建了它,就像我们通常使用addJavascriptInterface一样。
webview.addJavascriptInterface(JsObject(presenter), "Android”)

这样,我们已经建立了通信桥梁,所有通过postMessage发送的消息都会到达已订阅该侦听器的接口。
class JsObject(val presenter: Presenter) {
    @JavascriptInterface
    fun receiveMessage(data: String): Boolean {
        presenter.onDataReceived(data)
        Log.d("Data from JS", data)
        return true
    }

1

您需要使用一个中间的抽象接口,在一种实现中通过PostMessage处理消息,在另一种情况下通过addJavascriptInterface处理。

window.addEventListener("message", onReceivedPostMessage, false);

function onReceivedPostMessage(event){
     //..ex deconstruct event into action & params
     var action = event.data.action;
     var params = event.data.params;
     performAction(action, params); //performAction would be the uniform API
}

function onReceivedActivityMessageViaJavascriptInterface(json){
     //..ex deconstruct data into action & params
     var data = JSON.parse(json); 
     var action = data.action;
     var params = data.params;
     performAction(action, params); //performAction would be the uniform API
}

感谢您发布这个问题。由于我已经换了公司,无法再进行测试,但如果其他人可以确认它对他们有用,我会将其标记为“已接受”。 - Brian Putnam
我从未收到postMessage的事件。我需要在Webview上设置什么才能让它发生? - lostintranslation
@lostintranslation - 你可能已经离开了,但以防万一有人关注...你正在使用webView.addJavascriptInterface来增加一个全局JavaScript对象。你需要像通常注册监听器(例如window.addEventListener)然后调用该对象-定义在该对象上适当处理你的信息的任何方法。我提供了另一个例子作为备选答案(https://dev59.com/7WIk5IYBdhLWcg3wkezM#39879882),其中包含更详细的说明。 - darrin

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接