Google的security guidelines适用于Android应用开发者的内容如下:
WebViews不会将
addJavaScriptInterface()
与不受信任的内容一起使用。在Android M及更高版本上,可以使用HTML消息频道。
我可以说," HTML消息频道"是指createWebMessageChannel()
,WebMessagePort
,WebMessage
和亲属之类的内容。
但是,他们没有提供任何例子。他们只是链接到WhatWG specification,这是相当不清楚的。而且,根据Google搜索createWebMessageChannel
,它似乎尚未被使用 - 我的blog post describing changes in the Android 6.0 SDK排名前10位,我只是顺便提及。
addJavascriptInterface()
用于允许WebView
中的JavaScript使用WebView
调用应用程序提供的Java代码。我们如何使用" HTML消息频道"作为替代品?
答案 0 :(得分:7)
好的,我有这个工作,虽然它有点糟糕。
第1步:使用WebView
填充loadDataWithBaseURL()
。 loadUrl()
无效,http
。您需要使用https
或loadDataWithBaseURL()
网址作为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()
。 Uri
将Uri
作为第二个参数,此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
(其中WebView
是THIS_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);