BridgeWebView.java 10.6 KB
package com.github.lzyzsd.jsbridge;

import android.annotation.SuppressLint;
import android.content.Context;
import android.os.Build;
import android.os.Looper;
import android.os.SystemClock;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.webkit.JavascriptInterface;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;

import androidx.collection.ArrayMap;

import com.google.gson.Gson;
import com.wd.foundation.wdkitcore.tools.AppContext;
import com.wd.foundation.wdkitcore.tools.StringUtils;

import org.json.JSONObject;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

@SuppressLint("SetJavaScriptEnabled")
public class BridgeWebView extends WebView implements WebViewJavascriptBridge, BridgeWebViewClient.OnLoadJSListener {

	private final int URL_MAX_CHARACTER_NUM=2097152;
    private Map<String, OnBridgeCallback> mCallbacks = new ArrayMap<>();

    private List<Object> mMessages = new ArrayList<>();

    private BridgeWebViewClient mClient;

    private long mUniqueId = 0;

    private boolean mJSLoaded = false;

    private Gson mGson;

    public BridgeWebView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public BridgeWebView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init();
    }

    public BridgeWebView(Context context) {
        super(context);
        init();
    }

    private void init() {
//        clearCache(true);
//        getSettings().setUseWideViewPort(true);
////		webView.getSettings().setLoadWithOverviewMode(true);
//        getSettings().setCacheMode(WebSettings.LOAD_DEFAULT);
//        getSettings().setJavaScriptEnabled(true);
////        mContent.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
//        getSettings().setJavaScriptCanOpenWindowsAutomatically(true);

//        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT && BuildConfig.DEBUG) {
//            WebView.setWebContentsDebuggingEnabled(true);
//        }

        WebSettings mWebSettings = this.getSettings();
        /**
         * 增加useragent尾部标志;tag=anhuiAPP,以供h5区分渠道
         */
        mWebSettings.setUserAgentString(getUA(this));

        mWebSettings.setSupportZoom(false);
        mWebSettings.setDefaultTextEncodingName("utf-8");
        mWebSettings.setUseWideViewPort(true);
        mWebSettings.setLoadsImagesAutomatically(true);
        mWebSettings.setJavaScriptEnabled(true);
        mWebSettings.setBlockNetworkLoads(false);
        mWebSettings.setLayoutAlgorithm(WebSettings.LayoutAlgorithm.SINGLE_COLUMN);
        mWebSettings.setMediaPlaybackRequiresUserGesture(false);
        mWebSettings.setMixedContentMode(WebSettings.LOAD_DEFAULT);
        setLayerType(View.LAYER_TYPE_HARDWARE, null);

        mWebSettings.setDatabaseEnabled(true);
        mWebSettings.setAllowFileAccess(true);
        mWebSettings.setDomStorageEnabled(true);
        // 1. 设置缓存路径
        String cacheDirPath = AppContext.getContext().getFilesDir().getAbsolutePath()+"/cache/web/";
        mWebSettings.setAppCachePath(cacheDirPath);
        mWebSettings.setDatabasePath(cacheDirPath);
        // 2. 设置缓存大小为200M
        mWebSettings.setAppCacheMaxSize(200*1024*1024);
        // 3. 开启Application Cache存储机制
        mWebSettings.setAppCacheEnabled(true);

        /**
         * LOAD_DEFAULT:使用默认的缓存模式,即按照网站的缓存策略来加载网页。
         * LOAD_NO_CACHE:不使用缓存,每次都重新从网络上获取数据。
         * LOAD_CACHE_ONLY:只使用缓存,不从网络上获取数据。
         * LOAD_CACHE_ELSE_NETWORK:会先使用缓存数据,如果缓存数据过期了再从网络上获取数据。
         */
        mWebSettings.setCacheMode(WebSettings.LOAD_DEFAULT);
        mWebSettings.setSupportMultipleWindows(false);
        mWebSettings.setJavaScriptCanOpenWindowsAutomatically(true);



        mClient = new BridgeWebViewClient(this);
        super.setWebViewClient(mClient);
    }

    public  String getUA(WebView mWebView){
        try {
            WebSettings mWebSettings = mWebView.getSettings();
            String userAgentString = mWebSettings.getUserAgentString();
            StringBuilder stringBuilder = new StringBuilder();
            stringBuilder.append(userAgentString);
            stringBuilder.append(";tag=dailyChinaNewspaper");
//            stringBuilder.append("&version=" + DeviceUtil.getVersionName());
            stringBuilder.append("&platform=Android");
//            Logger.t(TAG).d("useragent===>"+stringBuilder.toString());
            return stringBuilder.toString();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return "";
    }

    public void setGson(Gson gson) {
        mGson = gson;
    }

    public boolean isJSLoaded() {
        return mJSLoaded;
    }

    public Map<String, OnBridgeCallback> getCallbacks() {
        return mCallbacks;
    }

    @Override
    public void setWebViewClient(WebViewClient client) {
        mClient.setWebViewClient(client);
    }

    @Override
    public void onLoadStart() {
        mJSLoaded = false;
    }

    @Override
    public void onLoadFinished() {
        mJSLoaded = true;
        if (mMessages != null) {
            for (Object message : mMessages) {
                dispatchMessage(message);
            }
            mMessages = null;
        }
    }

    @Override
    public void sendToWeb(String data) {
        sendToWeb(data, (OnBridgeCallback) null);
    }

    @Override
    public void sendToWeb(String data, OnBridgeCallback responseCallback) {
        doSend(null, data, responseCallback);
    }

    /**
     * call javascript registered handler
     * 调用javascript处理程序注册
     *
     * @param handlerName handlerName
     * @param data        data
     * @param callBack    OnBridgeCallback
     */
    public void callHandler(String handlerName, String data, OnBridgeCallback callBack) {
        doSend(handlerName, data, callBack);
    }


    @Override
    public void sendToWeb(String function, Object... values) {
        // 必须要找主线程才会将数据传递出去 --- 划重点
        if (Thread.currentThread() == Looper.getMainLooper().getThread()) {
            String jsCommand = String.format(function, values);
            jsCommand = String.format(BridgeUtil.JAVASCRIPT_STR, jsCommand);
            loadUrl(jsCommand);
        }
    }

    /**
     * 保存message到消息队列
     *
     * @param handlerName      handlerName
     * @param data             data
     * @param responseCallback OnBridgeCallback
     */
    private void doSend(String handlerName, String data, OnBridgeCallback responseCallback) {
        JSRequest request = new JSRequest();
        if (StringUtils.isNotBlank(data)) {
            request.data = data;
        }
        if (responseCallback != null) {
            String callbackId = String.format(BridgeUtil.CALLBACK_ID_FORMAT, (++mUniqueId) + (BridgeUtil.UNDERLINE_STR + SystemClock.currentThreadTimeMillis()));
           try {
               mCallbacks.put(callbackId, responseCallback);
           }catch (Exception e){
               e.printStackTrace();
               return;
           }
           request.callbackId = callbackId;
        }
        if (!TextUtils.isEmpty(handlerName)) {
            request.handlerName = handlerName;
        }
        queueMessage(request);
    }

    /**
     * list<message> != null 添加到消息集合否则分发消息
     *
     * @param message Message
     */
    private void queueMessage(Object message) {
        if (mMessages != null) {
            mMessages.add(message);
        } else {
            dispatchMessage(message);
        }
    }

    /**
     * 分发message 必须在主线程才分发成功
     *
     * @param message Message
     */
    private void dispatchMessage(Object message) {
        if (mGson == null){
            return;
        }
        String messageJson = mGson.toJson(message);
        //escape special characters for json string  为json字符串转义特殊字符

		  // 系统原生 API 做 Json转义,没必要自己正则替换,而且替换不一定完整
        messageJson = JSONObject.quote(messageJson);
        String javascriptCommand = String.format(BridgeUtil.JS_HANDLE_MESSAGE_FROM_JAVA, messageJson);

        // 必须要找主线程才会将数据传递出去 --- 划重点
        if (Thread.currentThread() == Looper.getMainLooper().getThread()) {
			if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
				this.evaluateJavascript(javascriptCommand,null);
			}else {
				this.loadUrl(javascriptCommand);
			}
        }
    }

    public void sendResponse(Object data, String callbackId) {
        if (!(data instanceof String) && mGson == null){
            return;
        }
        if (!TextUtils.isEmpty(callbackId)) {
            final JSResponse response = new JSResponse();
            response.responseId = callbackId;
            response.responseData = data instanceof String ? (String) data : mGson.toJson(data);
            if (Thread.currentThread() == Looper.getMainLooper().getThread()){
                dispatchMessage(response);
            }else {
                post(new Runnable() {
                    @Override
                    public void run() {
                        dispatchMessage(response);
                    }
                });
            }

        }
    }

    @Override
    public void destroy() {
        super.destroy();
        mCallbacks.clear();
    }

    public static abstract class BaseJavascriptInterface {

        private Map<String, OnBridgeCallback> mCallbacks;

        public BaseJavascriptInterface(Map<String, OnBridgeCallback> callbacks) {
            mCallbacks = callbacks;
        }

        @JavascriptInterface
        public String send(String data, String callbackId) {
            Log.d("BaseJavascriptInterface", data + ", callbackId: " + callbackId + " " + Thread.currentThread().getName());
            return send(data);
        }

        @JavascriptInterface
        public void response(String data, String responseId) {
            Log.d("BaseJavascriptInterface", data + ", responseId: " + responseId + " " + Thread.currentThread().getName());
            if (!TextUtils.isEmpty(responseId)) {
                OnBridgeCallback function = mCallbacks.remove(responseId);
                if (function != null) {
                    function.onCallBack(data);
                }
            }
        }

        public abstract String send(String data);
    }

}