com.ronnywu.browserview.client.Lv99JavascriptBridgeWebViewClient.java Source code

Java tutorial

Introduction

Here is the source code for com.ronnywu.browserview.client.Lv99JavascriptBridgeWebViewClient.java

Source

/*
 * 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;
    }
}