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

时间:2017-01-19 22:46:37

标签: android android-webview

Google的security guidelines适用于Android应用开发者的内容如下:

  

WebViews不会将addJavaScriptInterface()与不受信任的内容一起使用。

     

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

我可以说," HTML消息频道"是指createWebMessageChannel()WebMessagePortWebMessage和亲属之类的内容。

但是,他们没有提供任何例子。他们只是链接到WhatWG specification,这是相当不清楚的。而且,根据Google搜索createWebMessageChannel,它似乎尚未被使用 - 我的blog post describing changes in the Android 6.0 SDK排名前10位,我只是顺便提及。

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

4 个答案:

答案 0 :(得分:7)

好的,我有这个工作,虽然它有点糟糕。

第1步:使用WebView填充loadDataWithBaseURL()loadUrl()无效,http。您需要使用httpsloadDataWithBaseURL()网址作为file的第一个参数(或者至少不是private static final String,因为错误)。您稍后将需要该网址,因此请保留该网址(例如addJavascriptInterface()值)。

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

步骤3:在您要初始化这些通信时,请致电WebView上的WebMessagePort[],以创建setWebMessageCallback()。该数组中的第0个元素是通信管道的末尾,您可以在其上调用WebMessagePort[],以便能够响应从JavaScript发送给您的消息。

步骤#4:将WebMessage中的第一个元素包装到JavaScript postWebMessage()并在WebView上调用postWebMessage()UriUri作为第二个参数,此loadDataWithBaseURL()必须从您在步骤1中使用的相同网址中导出,作为 @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)); } 的基本网址。< / p>

wv

(其中WebViewTHIS_IS_STUPID,而loadDataWithBaseURL()是与onmessage一起使用的网址

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

步骤#6:如果要将消息从JavaScript发送到Java,请从步骤#5的端口上调用setWebMessageCallback(),该消息将传递给您使用{{1}注册的回调在步骤#3中。

var port;

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

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

    port.onmessage = function (f) {
        parse(e.data);
    }
}

bugs演示了这项技巧。它有WebView,显示基于环境光传感器的当前光照水平。传感器数据在推送的基础上(当传感器改变时)或拉动基础(用户点击网页上的“光级”标签)被馈送到WebView。此应用程序在Android 6.0+设备上使用WebMessagePort,但推送选项已注释掉,因此您可以确认拉动方法是通过端口工作的。我将在即将发布的This sample app版本中详细介绍示例应用程序。

答案 1 :(得分:5)

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

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

答案 2 :(得分:3)

以下是使用compat lib的解决方案: Download Full Solution in Android Studio format

此示例使用存储在资产文件夹中的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();

这是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>

最后这是本地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)
    }
}

从“ WebViewClient.onPageFinished(webView:WebView,url:String)”回调执行本机代码很重要。 有关完整的详细信息,请参见上面的下载链接。该项目显示了postMessage双向运行(对于JS而言是本地的,对于JS而言是原生的) 希望这会有所帮助。

答案 3 :(得分:1)

@CommonsWare 我已经尝试过你的解决方案,它对我有用。只是一点点补充。通过将loadUrl()参数设置为Uri,您也可以使用Uri.EMPTY。使用Nexus 7(MOB30J)。

    getWebView().postWebMessage(new WebMessage("MESSAGE", new WebMessagePort[]{
            channel[1]
    }), Uri.EMPTY);