package com.fangjian; import android.app.Activity; import android.util.Log; import android.webkit.ConsoleMessage; import android.webkit.JavascriptInterface; import android.webkit.JsResult; import android.webkit.WebChromeClient; import android.webkit.WebSettings; import android.webkit.WebView; import android.webkit.WebViewClient; import android.widget.Toast; import org.json.JSONObject; import java.io.Serializable; import java.util.HashMap; import java.util.Map; /** * Created with IntelliJ IDEA. * implements Serializable in case of javascript interface will be removed in obfuscated code. * * User: jack_fang * Date: 13-8-15 * Time: 下午6:08 */ public class WebViewJavascriptBridge implements Serializable { WebView mWebView; Activity mContext; WVJBHandler _messageHandler; Map<String,WVJBHandler> _messageHandlers; Map<String,WVJBResponseCallback> _responseCallbacks; long _uniqueId; public WebViewJavascriptBridge(Activity context,WebView webview,WVJBHandler handler, WebViewClient webViewClient) { this.mContext=context; this.mWebView=webview; this._messageHandler=handler; _messageHandlers=new HashMap<String,WVJBHandler>(); _responseCallbacks=new HashMap<String, WVJBResponseCallback>(); _uniqueId=0; WebSettings webSettings = mWebView.getSettings(); webSettings.setJavaScriptEnabled(true); /*if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { mWebView.setWebContentsDebuggingEnabled(true); }*/ mWebView.addJavascriptInterface(this, "_WebViewJavascriptBridge"); mWebView.setWebViewClient(webViewClient); mWebView.setWebChromeClient(new MyWebChromeClient()); //optional, for show console and alert } private class MyWebChromeClient extends WebChromeClient { @Override public boolean onConsoleMessage(ConsoleMessage cm) { Log.d("JSBridge", cm.message() + " line: " + cm.lineNumber()); return true; } @Override public boolean onJsAlert(WebView view, String url, String message, JsResult result) { // if don't cancel the alert, webview after onJsAlert not responding taps // you can check this : // http://stackoverflow.com/questions/15892644/android-webview-after-onjsalert-not-responding-taps result.cancel(); Toast.makeText(mContext, message, Toast.LENGTH_SHORT).show(); return true; } } public interface WVJBHandler{ public void handle(String data,WVJBResponseCallback jsCallback); } public interface WVJBResponseCallback{ public void callback(String data); } public void registerHandler(String handlerName,WVJBHandler handler) { _messageHandlers.put(handlerName,handler); } private class CallbackJs implements WVJBResponseCallback{ private final String callbackIdJs; public CallbackJs(String callbackIdJs){ this.callbackIdJs=callbackIdJs; } @Override public void callback(String data) { _callbackJs(callbackIdJs,data); } } private void _callbackJs(String callbackIdJs,String data) { //TODO: CALL js to call back; Map<String,String> message=new HashMap<String, String>(); message.put("responseId",callbackIdJs); message.put("responseData",data); _dispatchMessage(message); } @JavascriptInterface public void _handleMessageFromJs(String data,String responseId, String responseData,String callbackId,String handlerName){ if (null!=responseId) { WVJBResponseCallback responseCallback = _responseCallbacks.get(responseId); responseCallback.callback(responseData); _responseCallbacks.remove(responseId); } else { WVJBResponseCallback responseCallback = null; if (null!=callbackId) { responseCallback=new CallbackJs(callbackId); } WVJBHandler handler; if (null!=handlerName) { handler = _messageHandlers.get(handlerName); if (null==handler) { Log.e("JSBridge","WVJB Warning: No handler for "+handlerName); return ; } } else { handler = _messageHandler; } try { final String tmpData = data; final WVJBResponseCallback tmpResponseCallback = responseCallback; final WVJBHandler tmpHandler = handler; mContext.runOnUiThread(new Runnable(){ @Override public void run() { tmpHandler.handle(tmpData, tmpResponseCallback); } }); }catch (Exception exception) { Log.e("JSBridge","WebViewJavascriptBridge: WARNING: java handler threw. "+exception.getMessage()); } } } public void send(String data) { send(data,null); } public void send(String data ,WVJBResponseCallback responseCallback) { _sendData(data,responseCallback,null); } private void _sendData(String data,WVJBResponseCallback responseCallback,String handlerName){ Map <String, String> message=new HashMap<String,String>(); message.put("data",data); if (null!=responseCallback) { String callbackId = "java_cb_"+ (++_uniqueId); _responseCallbacks.put(callbackId,responseCallback); message.put("callbackId",callbackId); } if (null!=handlerName) { message.put("handlerName", handlerName); } _dispatchMessage(message); } private void _dispatchMessage(Map <String, String> message){ String messageJSON = new JSONObject(message).toString(); Log.d("JSBridge","sending:"+messageJSON); final String javascriptCommand = String.format("javascript:WebViewJavascriptBridge._handleMessageFromJava('%s');",doubleEscapeString(messageJSON)); mContext.runOnUiThread(new Runnable(){ @Override public void run() { mWebView.loadUrl(javascriptCommand); } }); } public void callHandler(String handlerName) { callHandler(handlerName, null, null); } public void callHandler(String handlerName,String data) { callHandler(handlerName, data,null); } public void callHandler(String handlerName,String data,WVJBResponseCallback responseCallback){ _sendData(data, responseCallback,handlerName); } /* * you must escape the char \ and char ", or you will not recevie a correct json object in * your javascript which will cause a exception in chrome. * * please check this and you will know why. * http://stackoverflow.com/questions/5569794/escape-nsstring-for-javascript-input * http://www.json.org/ */ private String doubleEscapeString(String javascript) { String result; result = javascript.replace("\\", "\\\\"); result = result.replace("\"", "\\\""); result = result.replace("\'", "\\\'"); result = result.replace("\n", "\\n"); result = result.replace("\r", "\\r"); result = result.replace("\f", "\\f"); return result; } }