The evaluateJavascript method is very helpful but does not handle all use cases in which you may need to execute native Java code initiated by your web page. One use case would be handling web events (like a button click) or for returning data from a method on Android Jelly Bean or below.
One word of warning before we begin, allowing an HTML page to execute native Java code is inherently insecure. Web pages are isolated from the app they're running in by design. Creating a bridge between the web page and the app means that javascript on the page may have the ability to do anything on the device for which the app that's hosting the web page has permissions for. It is extremely important that you keep this in mind when creating the bridge from the web page to your app.
There are three main things that you need to do in order to call native Java methods from javascript:
- Create a javascript interface object which serves as the bridge between javascript and Java.
- Add the interface to the Web View so that any page that it hosts can use the bridge.
- Update your javascript to use the bridge.
Creating a Javascript Interface Object
In this very basic class I am declaring a listener interface which I have the caller pass in via the constructor. This is one way to make sure that you only allow javascript to interact with your app in the way you expect. By using a well defined interface you are making it more difficult for malicious javascript to execute arbitrary code.
To make a method visible to javascript the method needs to be decorated with the @JavascriptInterface attribute.
Notice that I'm calling the listener back using a handler and runnable. This is because the bridge class methods aren't executing on the main thread. We need to execute the listener callback on the main thread.
public class WebViewNativeBridge
{
public interface OnSomeChangeListener {
public void onSomeChange(String text);
}
private OnSomeChangeListener listener = null;
public WebViewNativeBridge(OnSomeChangeListener listener) {
this.listener = listener;
}
@JavascriptInterface @SuppressWarnings("unused")
public void someChangeFromWeb(String text)
{
final String newText = text;
// This code is not executed in the UI thread
// so we must force it to happen
new Handler(Looper.getMainLooper()).post(new Runnable()
{
@Override
public void run()
{
if(listener != null)
listener.onSomeChange(newText);
}
});
}
}
Add The Javascript Bridge Object To Your WebView
The way you wire up your native javascript bridge is to add the javascript interface to your Web View using the addJavascriptInterface method. This method takes in two parameters, your bridge object and the key word you want to use to access the bridge from within javascript.
WebViewNativeBridge.OnSomeChangeListener listener
= this.getOnSomeChangeListener();
WebViewNativeBridge bridge = new WebViewNativeBridge(listener);
browser.addJavascriptInterface(bridge, "NativeBridge");
I created the bridge object with a custom listener I created:
private WebViewNativeBridge.OnSomeChangeListener getOnSomeChangeListener() {
return new WebViewNativeBridge.OnSomeChangeListener() {
public void onSomeChange(String val) {
// do something with the value passed in
}
};
}
Update The Javascript To Use The Native Bridge Object
The last step is to call into your native Java code from javascript. Using the NativeBridge object we registered with the Web View we can call the someChangeFromWeb method from our javascript. Note, the name of the object you use in javascript has to be exactly the same as the second parameter you passed into the addJavascriptInterface method.
function notifyNativeOfChange() {
NativeBridge.someChangeFromWeb("some change");
}