OK, I have this working, though it kinda sucks.
Step #1: Populate your WebView
using loadDataWithBaseURL()
. loadUrl()
will not work, because bugs. You need to use an http
or https
URL for the first parameter to loadDataWithBaseURL()
(or, at least, not file
, because bugs). And you will need that URL later, so hold onto it (e.g., private static final String
value).
Step #2: Decide when you want to initialize the communications from the JavaScript into Java. With addJavascriptInterface()
, this is available immediately. However, using WebMessagePort
is not nearly so nice. In particular, you cannot attempt to initialize the communications until the page is loaded (e.g., onPageFinished()
on a WebViewClient
).
Step #3: At the time that you want to initialize those communications, call createWebMessageChannel()
on the WebView
, to create a WebMessagePort[]
. The 0th element in that array is your end of the communications pipe, and you can call setWebMessageCallback()
on it to be able to respond to messages sent to you from JavaScript.
Step #4: Hand the 1st element in that WebMessagePort[]
to the JavaScript by wrapping it in a WebMessage
and calling postWebMessage()
on the WebView
. postWebMessage()
takes a Uri
as the second parameter, and this Uri
must be derived from the same URL that you used in Step #1 as the base URL for 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));
}
(where wv
is the WebView
and THIS_IS_STUPID
is the URL used with loadDataWithBaseURL()
)
Step #5: Your JavaScript can assign a function to the global onmessage
event, which will be called when postWebMessage()
is called. The 0th element of the ports
array that you get on the event will be the JavaScript end of the communications pipe, and you can stuff that in a variable somewhere. If desired, you can assign a function to onmessage
for that port, if the Java code will use the WebMessagePort
for sending over future data.
Step #6: When you want to send a message from JavaScript to Java, call postMessage()
on the port from Step #5, and that message will be delivered to the callback that you registered with setWebMessageCallback()
in step #3.
var port;
function pull() {
port.postMessage("ping");
}
onmessage = function (e) {
port = e.ports[0];
port.onmessage = function (f) {
parse(f.data);
}
}
This sample app demonstrates the technique. It has a WebView
that shows the current light level based on the ambient light sensor. That sensor data is fed into the WebView
either on a push basis (as the sensor changes) or on a pull basis (user taps the "Light Level" label on the Web page). This app uses WebMessagePort
for these on Android 6.0+ devices, though the push option is commented out so you can confirm that the pull approach is working through the port. I will have more detailed coverage of the sample app in an upcoming edition of my book.