如何使用WebMessagePort作为addJavascriptInterface()的替代方法?

Com*_*are 10 android android-webview

Google 针对Android应用开发者的安全指南包含以下内容:

WebView不addJavaScriptInterface()与不受信任的内容一起使用.

在Android M及更高版本上,可以使用HTML消息通道.

近我可以告诉大家,"HTML消息通道"是指喜欢的东西createWebMessageChannel(),WebMessagePort,WebMessage,和亲属.

但是,它们没有提供任何示例.他们所做的只是链接到WhatWG规范,这是相当不清楚的.而且,基于谷歌的搜索createWebMessageChannel,它似乎还没有被广泛使用 - 我的博客文章描述了Android 6.0 SDK中的变化,使得排名前十的搜索结果,我只是顺便提一下.

addJavascriptInterface()用于允许a中的JavaScript WebView使用应用程序调用应用程序提供的Java代码WebView.我们如何使用"HTML消息频道"代替它?

Com*_*are 10

好吧,我有这个工作,虽然它有点糟糕.

第1步:填充你的WebView使用loadDataWithBaseURL().loadUrl()不行,因为错误.您需要为第一个参数使用httphttpsURL loadDataWithBaseURL()(或者,至少不是file,因为错误).您稍后将需要该URL,因此请保留它(例如,private static final String值).

步骤2:确定何时要初始化从JavaScript到Java的通信.随着addJavascriptInterface(),这是立即可用.但是,使用WebMessagePort并不是那么好.特别是,在页面加载之前(例如,onPageFinished()在a上WebViewClient),您无法尝试初始化通信.

步骤3:在您想要初始化这些通信时,请createWebMessageChannel()打开WebView,创建一个WebMessagePort[].该数组中的第0个元素是通信管道的末尾,您可以调用setWebMessageCallback()它来响应从JavaScript发送给您的消息.

第4步:手的第1个要素WebMessagePort[]通过在包装它的JavaScript WebMessage和调用postWebMessage()WebView.postWebMessage()将a Uri作为第二个参数,并且Uri必须从您在步骤1中使用的相同URL作为基本URL loadDataWithBaseURL().

  @TargetApi(Build.VERSION_CODES.M)
  private void initPort() {
    final WebMessagePort[] channel=wv.createWebMessageChannel();

    port=channel[0];
    port.setWebMessageCallback(new WebMessagePort.WebMessageCallback() {
      @Override
      public void onMessage(WebMessagePort port, WebMessage message) {
        postLux();
      }
    });

    wv.postWebMessage(new WebMessage("", new WebMessagePort[]{channel[1]}),
          Uri.parse(THIS_IS_STUPID));
  }
Run Code Online (Sandbox Code Playgroud)

(其中,wvWebViewTHIS_IS_STUPID与所使用的URL loadDataWithBaseURL())

步骤5:您的JavaScript可以为全局onmessage事件分配一个函数,该函数将在调用时postWebMessage()调用.ports您在事件上获得的数组的第0个元素将是通信管道的JavaScript端,您可以将其填充到某个变量中.如果需要,您可以为该onmessage端口分配一个函数,如果Java代码将使用它WebMessagePort来发送未来数据.

步骤#6:当您想要从JavaScript向Java发送消息时,请postMessage()从步骤#5中调用端口,该消息将被传递到您setWebMessageCallback()在步骤3中注册的回调.

var port;

function pull() {
    port.postMessage("ping");
}

onmessage = function (e) {
    port = e.ports[0];

    port.onmessage = function (f) {
        parse(e.data);
    }
}
Run Code Online (Sandbox Code Playgroud)

此示例应用程序演示了该技术.它有一个WebView显示基于环境光传感器的当前光级.传感器数据WebView在推送的基础上(当传感器改变时)或在拉动的基础上(用户点击网页上的"光级"标签)输入.此应用程序WebMessagePort在Android 6.0+设备上使用这些应用程序,虽然推送选项已注释掉,因此您可以确认拉动方法正在通过端口工作.我将在即将出版的本书中详细介绍示例应用程序.

  • @Gohan:我对此表示怀疑,尽管对于 Android 4.4+ 来说这并不是不可能的。我的猜测是 Android 系统 WebView 应用程序具有 Web 端。我怀疑没有好的方法来创建一个支持库来以某种方式使 Java 端工作。4.4 之前的版本不太可能发生,因为我怀疑这些版本中的“WebView”是否对此有任何支持。但是,Google 的方式很神秘,因此,`́\_(ツ)_/́`。 (2认同)

Die*_*ano 6

在CTS中有一个测试

// Create a message channel and make sure it can be used for data transfer to/from js.
public void testMessageChannel() throws Throwable {
    if (!NullWebViewUtils.isWebViewAvailable()) {
        return;
    }
    loadPage(CHANNEL_MESSAGE);
    final WebMessagePort[] channel = mOnUiThread.createWebMessageChannel();
    WebMessage message = new WebMessage(WEBVIEW_MESSAGE, new WebMessagePort[]{channel[1]});
    mOnUiThread.postWebMessage(message, Uri.parse(BASE_URI));
    final int messageCount = 3;
    final CountDownLatch latch = new CountDownLatch(messageCount);
    runTestOnUiThread(new Runnable() {
        @Override
        public void run() {
            for (int i = 0; i < messageCount; i++) {
                channel[0].postMessage(new WebMessage(WEBVIEW_MESSAGE + i));
            }
            channel[0].setWebMessageCallback(new WebMessagePort.WebMessageCallback() {
                @Override
                public void onMessage(WebMessagePort port, WebMessage message) {
                    int i = messageCount - (int)latch.getCount();
                    assertEquals(WEBVIEW_MESSAGE + i + i, message.getData());
                    latch.countDown();
                }
            });
        }
    });
    // Wait for all the responses to arrive.
    boolean ignore = latch.await(TIMEOUT, java.util.concurrent.TimeUnit.MILLISECONDS);
}
Run Code Online (Sandbox Code Playgroud)

档案:cts/tests/tests/webkit/src/android/webkit/cts/PostMessageTest.java.至少有一些起点.


小智 5

这是使用 compat 库的 解决方案:以 Android Studio 格式下载完整解决方案

此示例使用存储在资产文件夹中的 index.html 和 index.js 文件。

这是JS:

const channel = new MessageChannel();
var nativeJsPortOne = channel.port1;
var nativeJsPortTwo = channel.port2;
window.addEventListener('message', function(event) {
    if (event.data != 'capturePort') {
        nativeJsPortOne.postMessage(event.data)
    } else if (event.data == 'capturePort') {
        /* The following three lines form Android class 'WebViewCallBackDemo' capture the port and assign it to nativeJsPortTwo
        var destPort = arrayOf(nativeToJsPorts[1])
        nativeToJsPorts[0].setWebMessageCallback(nativeToJs!!)
        WebViewCompat.postWebMessage(webView, WebMessageCompat("capturePort", destPort), Uri.EMPTY) */
        if (event.ports[0] != null) {
            nativeJsPortTwo = event.ports[0]
        }
    }
}, false);

nativeJsPortOne.addEventListener('message', function(event) {
    alert(event.data);
}, false);

nativeJsPortTwo.addEventListener('message', function(event) {
    alert(event.data);
}, false);
nativeJsPortOne.start();
nativeJsPortTwo.start();
Run Code Online (Sandbox Code Playgroud)

这是 HTML:

<!DOCTYPE html>
<html lang="en-gb">
<head>
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>WebView Callback Demo</title>
    <script src="js/index.js"></script>
</head>
<body>
    <div style="font-size: 24pt; text-align: center;">
        <input type="button" value="Test" onclick="nativeJsPortTwo.postMessage(msgFromJS.value);" style="font-size: inherit;" /><br />
        <input id="msgFromJS" type="text" value="JavaScript To Native" style="font-size: 16pt; text-align: inherit; width: 80%;" />
    </div>
</body>
</html>
Run Code Online (Sandbox Code Playgroud)

最后这里是原生 Android 代码:

class PostMessageHandler(webView: WebView) {
    private val nativeToJsPorts = WebViewCompat.createWebMessageChannel(webView)
    private var nativeToJs: WebMessagePortCompat.WebMessageCallbackCompat? = null
    init {
        if (WebViewFeature.isFeatureSupported(WebViewFeature.WEB_MESSAGE_CALLBACK_ON_MESSAGE)) {
            nativeToJs = object : WebMessagePortCompat.WebMessageCallbackCompat() {
                override fun onMessage(port: WebMessagePortCompat, message: WebMessageCompat?) {
                    super.onMessage(port, message)
                    Toast.makeText(webView.context, message!!.data, Toast.LENGTH_SHORT).show()
                }
            }
        }
        var destPort = arrayOf(nativeToJsPorts[1])
        nativeToJsPorts[0].setWebMessageCallback(nativeToJs!!)
        WebViewCompat.postWebMessage(webView, WebMessageCompat("capturePort", destPort), Uri.EMPTY)
    }
}
Run Code Online (Sandbox Code Playgroud)

从 'WebViewClient.onPageFinished(webView: WebView, url: String)' 回调执行本机代码很重要。有关完整详细信息,请参阅上面的下载链接。该项目显示 postMessage 以两种方式工作(本机到 JS 和 JS 到本机)希望这会有所帮助。