Java tutorial
/* * Copyright (C) 2016 RonnyWu.Pe@Gmail.com * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.ronnywu.browserview.client; import android.os.Build; import android.webkit.ValueCallback; import android.webkit.WebView; import android.widget.Toast; import com.ronnywu.browserview.entity.WVJBMessage; import com.ronnywu.browserview.interfaces.JsCallHandler; import com.ronnywu.browserview.interfaces.JsCallback; import com.ronnywu.browserview.interfaces.ReceiveMessagesListener; import com.ronnywu.browserview.interfaces.ReplyMessageListener; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.HashMap; import java.util.Map; /** * JavaScript . * {@link Lv5LazyImageWebViewClient} ,,APP JS ?. * * @author RonnyWu.Pe@Gmail.com * @version 1.0 2016-06-16 * @see android.webkit.WebViewClient * @see com.ronnywu.browserview.client.Lv5LazyImageWebViewClient * @see com.ronnywu.browserview.view.Lv99JavascriptBridgeBrowserView */ public class Lv99JavascriptBridgeWebViewClient extends Lv10LoadListenerWebViewClient { /** * ?, JS , JS ??. */ private static final String kInterface = "WVJBInterface"; /** * ?,? Scheme . */ private static final String kCustomProtocolScheme = "wvjbscheme"; /** * ?,??,????. */ private static final String kQueueHasMessage = "__WVJB_QUEUE_MESSAGE__"; /** * ? JS ?, false ,??. */ private boolean openJSBridge = false; /** * JS ? JS ,?. */ private ArrayList<WVJBMessage> startupMessageQueue = null; /** * ?? JS ?, APP ?. */ private Map<String, ReplyMessageListener> messageFromAndroidToJavaScriptInterfaceMap = null; /** * ?? APP ????. */ private Map<String, ReceiveMessagesListener> messageFromJavaScriptToAndroidInterfaceMap = null; /** * ID. ??,ID1. */ private long uniqueId = 0; /** * JS ? APP ??. * * @see {@link ReceiveMessagesListener} */ private ReceiveMessagesListener messageHandler; /** * APP? JS ?. */ private JsCallHandler jsCallHandler = new JsCallHandler(); /** * ?? WebView . */ private WebView webView; /** * . * * @param webView ? WebView . */ public Lv99JavascriptBridgeWebViewClient(WebView webView) { this(webView, null); } /** * @param webView * @param messageHandler */ public Lv99JavascriptBridgeWebViewClient(WebView webView, ReceiveMessagesListener messageHandler) { this.webView = webView; this.webView.addJavascriptInterface(jsCallHandler, kInterface); this.messageFromAndroidToJavaScriptInterfaceMap = new HashMap<>(); this.messageFromJavaScriptToAndroidInterfaceMap = new HashMap<>(); this.startupMessageQueue = new ArrayList<>(); this.messageHandler = messageHandler; } /** * JS? * * @param handlerName JS?? * @param jsonStr ??? * @param responseCallback JS */ public void send(String jsonStr, ReplyMessageListener responseCallback, String handlerName) { sendData(jsonStr, responseCallback, handlerName); } /** * JS? * * @param handlerName ???(,JS??) * @param redAgateJsMessageHandler JS? */ public void registerHandler(String handlerName, ReceiveMessagesListener redAgateJsMessageHandler) { if (handlerName == null || handlerName.length() == 0 || redAgateJsMessageHandler == null) { return; } messageFromJavaScriptToAndroidInterfaceMap.put(handlerName, redAgateJsMessageHandler); } /** * ????.? JS ?????? * " # Red Agate # Browser # The BrowserView JS Bridge is Close!". * * @param data ? * @param responseCallback ?. * @param handlerName JS ??, APP JS ??. */ private void sendData(String data, ReplyMessageListener responseCallback, String handlerName) { if (!openJSBridge) { Toast.makeText(webView.getContext(), " # Red Agate # Browser " + "# The BrowserView JS Bridge is Close!", Toast.LENGTH_LONG) .show(); return; } if (data == null && (handlerName == null || handlerName.length() == 0)) { return; } WVJBMessage message = new WVJBMessage(); if (data != null) { message.data = data; } if (responseCallback != null) { String callbackId = "objc_cb_" + (++uniqueId); messageFromAndroidToJavaScriptInterfaceMap.put(callbackId, responseCallback); message.callbackId = callbackId; } if (handlerName != null) { message.handlerName = handlerName; } queueMessage(message); } /** * ? {@link WVJBMessage} ?. * ?????,?. * ???,??. * * @param message {@link WVJBMessage}?. */ private void queueMessage(WVJBMessage message) { if (startupMessageQueue != null) { startupMessageQueue.add(message); } else { dispatchMessage(message); } } /** * ???,?,??JS????. * ?,?? JsonString JS . * * @param message {@link WVJBMessage}?. */ private void dispatchMessage(WVJBMessage message) { String messageJSON = message2JSONObject(message).toString().replaceAll("\\\\", "\\\\\\\\") .replaceAll("\"", "\\\\\"").replaceAll("\'", "\\\\\'").replaceAll("\n", "\\\\\n") .replaceAll("\r", "\\\\\r").replaceAll("\f", "\\\\\f"); executeJavascript("WebViewJavascriptBridge._handleMessageFromObjC('" + messageJSON + "');"); } /** * ? {@link WVJBMessage} ? {@link JSONObject} . * {@link #JSONObject2WVJBMessage(JSONObject)} ??. * * @param message JS?. * @return {@link JSONObject} . */ private JSONObject message2JSONObject(WVJBMessage message) { JSONObject jo = new JSONObject(); try { if (message.callbackId != null) { jo.put("callbackId", message.callbackId); } if (message.data != null) { jo.put("data", message.data); } if (message.handlerName != null) { jo.put("handlerName", message.handlerName); } if (message.responseId != null) { jo.put("responseId", message.responseId); } if (message.responseData != null) { jo.put("responseData", message.responseData); } } catch (JSONException e) { e.printStackTrace(); } return jo; } /** * {@link JSONObject} ? ? {@link WVJBMessage}. * {@link #message2JSONObject(WVJBMessage)} ??. * * @param jo {@link JSONObject} . * @return {@link WVJBMessage} . */ private WVJBMessage JSONObject2WVJBMessage(JSONObject jo) { WVJBMessage message = new WVJBMessage(); try { if (jo.has("callbackId")) { message.callbackId = jo.getString("callbackId"); } if (jo.has("data")) { message.data = jo.get("data").toString(); } if (jo.has("handlerName")) { message.handlerName = jo.getString("handlerName"); } if (jo.has("responseId")) { message.responseId = jo.getString("responseId"); } if (jo.has("responseData")) { message.responseData = jo.get("responseData").toString(); } } catch (JSONException e) { e.printStackTrace(); } return message; } /** * ??,JS ??,??, {@link #processQueueMessage(String)} * ??. * * @see {@link #processQueueMessage(String)} ??. */ private void flushMessageQueue() { String script = "WebViewJavascriptBridge._fetchQueue()"; executeJavascript(script, new JsCallback() { public void onReceiveValue(String messageQueueString) { if (messageQueueString == null || messageQueueString.length() == 0) { return; } processQueueMessage(messageQueueString); } }); } /** * ???. * * @param messageQueueString ??. */ private void processQueueMessage(String messageQueueString) { try { JSONArray messages = new JSONArray(messageQueueString); for (int i = 0; i < messages.length(); i++) { JSONObject jo = messages.getJSONObject(i); WVJBMessage wvjbMessage = JSONObject2WVJBMessage(jo); if (wvjbMessage.responseId != null) { ReplyMessageListener responseCallback = messageFromAndroidToJavaScriptInterfaceMap .remove(wvjbMessage.responseId); if (responseCallback != null) { responseCallback.onReplyMessage(wvjbMessage.responseData); } } else { ReplyMessageListener responseCallback = null; if (wvjbMessage.callbackId != null) { final String callbackId = wvjbMessage.callbackId; responseCallback = new ReplyMessageListener() { @Override public void onReplyMessage(String message) { WVJBMessage wvjbMessage = new WVJBMessage(); wvjbMessage.responseId = callbackId; wvjbMessage.responseData = message; queueMessage(wvjbMessage); } }; } ReceiveMessagesListener receiveMessagesListener; if (wvjbMessage.handlerName != null) { receiveMessagesListener = messageFromJavaScriptToAndroidInterfaceMap .get(wvjbMessage.handlerName); } else { receiveMessagesListener = messageHandler; } if (receiveMessagesListener != null) { receiveMessagesListener.onReceivedMessage(wvjbMessage.data, responseCallback); } } } } catch (JSONException e) { e.printStackTrace(); } } /** * JS . * ??, {@link #executeJavascript(String, JsCallback)}. * * @param script JS . */ public void executeJavascript(String script) { executeJavascript(script, null); } /** * JS . * * @param script JS . * @param redAgateJSCallback JS . */ public void executeJavascript(String script, final JsCallback redAgateJSCallback) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { webView.evaluateJavascript(script, new ValueCallback<String>() { @Override public void onReceiveValue(String value) { if (redAgateJSCallback != null) { if (value != null && value.startsWith("\"") && value.endsWith("\"")) { value = value.substring(1, value.length() - 1).replaceAll("\\\\", ""); } redAgateJSCallback.onReceiveValue(value); } } }); } else { if (redAgateJSCallback != null) { jsCallHandler.addCallback(++uniqueId + "", redAgateJSCallback); webView.loadUrl( "javascript:window." + kInterface + ".onResultForScript(" + uniqueId + "," + script + ")"); } else { webView.loadUrl("javascript:" + script); } } } /** * WebView , APP ?URL?. * ?Scheme {@link #kCustomProtocolScheme}, * Scheme true ,?. * <p/> * Scheme ?? {@link #kQueueHasMessage}, * ????,?,?? Scheme URL ?. * <p/> * {@link #flushMessageQueue()}?,? * * @param view ??. * @param url ??URL ?. */ @Override public boolean shouldOverrideUrlLoading(WebView view, String url) { // ?JS ?,URL'wvjbscheme',????URL if (openJSBridge && url.startsWith(kCustomProtocolScheme)) { // ??? if (url.indexOf(kQueueHasMessage) > 0) { flushMessageQueue(); } // true ??URL,?. return true; } else { return super.shouldOverrideUrlLoading(view, url); } } /** * ?. * ?APP,?. * ,?? JS . * <p/> * JS ?,???. * * @param view ?WebView * @param url URL?. */ @Override public void onPageFinished(WebView view, String url) { super.onPageFinished(view, url); if (!openJSBridge) { return; } try { InputStream is = webView.getContext().getAssets().open("JavascriptBridge.js.txt"); int size = is.available(); byte[] buffer = new byte[size]; is.read(buffer); is.close(); String js = new String(buffer); executeJavascript(js); } catch (IOException e) { Toast.makeText(webView.getContext(), " JavascriptBridge.js ?/", Toast.LENGTH_LONG).show(); return; } if (startupMessageQueue != null) { for (int i = 0; i < startupMessageQueue.size(); i++) { dispatchMessage(startupMessageQueue.get(i)); } startupMessageQueue = null; } } /** * ? JS ? APP ??. * * @return JS ? APP ??. * @see {@link ReceiveMessagesListener} */ public ReceiveMessagesListener getMessageHandler() { return messageHandler; } /** * JS ? APP ??. * * @param messageHandler JS ? APP ??. * @see {@link ReceiveMessagesListener} */ public void setMessageHandler(ReceiveMessagesListener messageHandler) { this.messageHandler = messageHandler; } /** * ???JS ?. * * @return true ?; false ?. */ public boolean isOpenJSBridge() { return openJSBridge; } /** * ??JS ?. * * @param openJSBridge true ?; false ??. */ public void setOpenJSBridge(boolean openJSBridge) { this.openJSBridge = openJSBridge; } }