Можно ли использовать API PostMessage для связи с Android WebView?

Я обычно использую HTML5 PostMessage API для передачи информации из моего содержимого iframed в родительский фрейм. Недавно мой контент использовался в Android WebView (насколько я могу судить, это эквивалент нативной версии iframe для Android). Есть ли способ для нативного приложения прослушивать события PostMessage, которые я им отправляю?

Я знаю, что addJavascriptInterface существует, я просто надеюсь, что есть способ повторно использовать мой существующий код PostMessage без написания чего-то нового.

5 ответов

Я понимаю, что этот вопрос старый, но я столкнулся с ним, поэтому решил, что отвечу здесь. Короче говоря - я обнаружил, что postMessage работает по крайней мере для связи от дочернего iframe до родительского окна, НО...

Оказывается, нам действительно не понравилось, как ведет себя iframe в WebView Android, поэтому вместо этого мы визуализировали содержимое iframe напрямую (как вы предлагаете). Это оставило нас с двумя проблемами: во-первых, у нас было много хуков обмена сообщениями от этого iframe до его родителя, а во-вторых, нам все еще нужно было обратиться к Android, чтобы отреагировать на эти события.

Вот пример сообщения из нашего кода, который был разбросан по всему iframe:

    parent.postMessage(JSON.stringify({
        action    : 'openModal',
        source    : embedId
    }), '*');

Когда мы находимся на Android, мы хотим использовать [поддержку андроида для интерфейсов javascript] ( https://developer.android.com/reference/android/webkit/WebView.html, java.lang.String)) для вставки объекта для обработки этого запроса при запуске в WebView.

На стороне 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(parent, JSON.stringify({
        action    : 'openModal',
        source    : embedId
    }), '*');

Теперь у нас есть один набор кода, который может быть запущен в iframe или Android WebView без изменений (по крайней мере, для этой части кода).

Я надеюсь, что это помогает кому-то.

Ответы здесь являются хорошими ответами на то время, когда они были опубликованы, но теперь, когда доступен androidx.webkit, я считаю, что это рекомендуемый подход.

Особенно, WebViewCompat.addWebMessageListener а также WebViewCompat.postWebMessage будет аналогом API PostMessage javascript.

Вот пример кода, скопированный из документации:

       // 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);
 }

У меня была аналогичная проблема, я хотел послушать .postMessageсобытие, происходящее в веб-просмотре. Я проверил в необработанном html, что событие было запущено следующим образом

window.parent.postMessage(JSON.stringify(message), '*');

Поэтому я копался в postMessage и нашел эту ссылку для добавления eventListener

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

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

  // ...
}

durbon Ответ durbon помог мне настроить Android JS Interface.

Во-первых, собственный класс для JavascriptInterface, подобный этому

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

Добавьте javascriptInterface в веб-просмотр перед загрузкой url/html

       webView.addJavascriptInterface(new JsObject(), "Android"); //"Android" is just a name

Затем вызовите javascript в onPageStarted обратный вызов WebViewClient как показано ниже

         @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);
  }

Недавно нам пришлось разработать проект, который должен был связать наше родное приложение Android с внешней интеграцией веб-просмотра от третьей стороны.

Идея, возникающая в связи с этим вопросом в stackru, очень интересна, особенно если вы не можете коснуться JS-кода этого веб-представления.

Я описываю, что мы сделали, чтобы иметь возможность передавать веб-просмотр в собственное приложение через шаги сообщения через JS PostMessage API.

Используя нашу реализацию webview. Мы реализовали метод onPageFinished, чтобы внедрить наш JS-код для загрузки Интернета.

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

По сути, мы создаем слушателя, который отправляет эти сообщения в наш собственный интерфейс JS и Android Bridge. Который мы ранее создали в настройке веб-просмотра в нашей деятельности Android, как обычно мы делаем с 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
    }

Вам необходимо использовать промежуточный абстрактный интерфейс, который в одной реализации обрабатывает сообщения через 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
}
Другие вопросы по тегам