The Art of Building Bridges
Android WebView Bridge in Depth
Bartłomiej Pisulak
Three types of Mobile Apps
Market Share
According to Gartner more than 50% of mobile apps deployed will be hybrid
ArchitectureIt’s all about WebView…
So what’s the difference?
The Bridge
Bridge between Native and JS
JS -‐> Native Native -‐> JS
JS_ObjectJavaScript -‐> Java
public class WebAppInterface { Context mContext;
/** Instantiate the interface and set the context */ WebAppInterface(Context c) { mContext = c; }
/** Show a toast from the web page */ @JavascriptInterface public void showToast(String toast) { Toast.makeText(mContext, toast, Toast.LENGTH_SHORT).show(); } }
Let’s build an interface:
JS_ObjectJavaScript -‐> Java
JS WebView
Quite good performance Use native bridging suggested by Google
Error prone
<script type="text/javascript"> function showAndroidToast(toast) { Android.showToast(toast); } </script>
WebSettings settings = webView.getSettings(); settings.setJavaScriptEnabled(true);
webView.addJavascriptInterface(new WebAppInterface(this), "Android");
Prompt JavaScript -‐> Java
JS WebChromeClient
Doesn’t crash the emulator It’s very slow
Overrides default prompt behaviour in WebView Criticised as antipattern
<script type="text/javascript"> prompt("toast", "This is Toast message"); </script> @Override
public boolean onJsPrompt (WebView view, String url, String message, String defaultValue, JsPromptResult result) { // message determines action if(message == "toast") { // make toast ... } }
loadUrl() Java -‐> JavaScript
Native JS
Quite fast Breaks inputs behaviour
<script type="text/javascript"> onLocationUpdate(int lat, int lng) { alert("Success: lat=" + lat + " lng: " + lng); } </script>
@Override public void onLocationChanged(Location location) { String lat = Double.toString(location.getLatitude()); String lng = Double.toString(location.getLongitude()); webView.loadUrl("javascript:onLocationUpdate(" + lat + "," + lng + ");"); }
Online Event Java -‐> JavaScript
Native JS
It’s fast Even faster than loadUrl()
Doesn’t break anything (yet) Problem in case of bridging more than one WebView
<script type="text/javascript"> var handleEvent = function() { // get result from native code // using exposed interface }
window.addEventListener("online", handleEvent); window.addEventListener("offline", handleEvent); </script>
boolean flipFlop; // (...) @Override public void informJavaScriptAboutResult() { flipFlop = !flipFlop; webView.setNetworkAvailable(flipFlop); }
Private APIJava -‐> JavaScript
Class webViewClass = WebView.class;
try { Field f = webViewClass.getDeclaredField("mProvider"); f.setAccessible(true); webViewObject = f.get(webView); webViewClass = webViewObject.getClass(); } catch (Throwable e) { // ... } try { Field f = webViewClass.getDeclaredField("mWebViewCore"); f.setAccessible(true); webViewCore = f.get(webViewObject);
if (webViewCore != null) { sendMessageMethod = webViewCore.getClass().getDeclaredMethod("sendMessage", Message.class); sendMessageMethod.setAccessible(true); } } catch (Throwable e) { // ... }
Be careful while using reflection…
Private APIJava -‐> JavaScript
Native JS
Super fast Not supported officially Works on Android 3.2+ May break in the future
<script type="text/javascript"> onLocationUpdate(int lat, int lng) { alert("Success: lat=" + lat + " lng: " + lng); } </script>
try { String js = "onLocationUpdate(" + lat + "," + lng + ");"; Message execJsMessage = Message.obtain(null, EXECUTE_JS, js); sendMessageMethod.invoke(webViewCore, execJsMessage); } catch (Throwable e) { // ... }
To Sum Up
Hybrid is the future
Choose proper bridge