1. 程式人生 > >JSBridge的原理與實現

JSBridge的原理與實現

為什麼要用 JSBridge

顧名思義,JSBridge是js和Native之間通訊的橋樑

  1. Android4.2以下,addJavascriptInterface方式有安全漏洞。
  2. url scheme互動方式是一套現有的成熟方案,可以完美相容各種版本,便於拓展,無重大安全性問題

另外,請注意,可以理解為JSBridge是一種互動理念,而上述的url scheme則是其中的一種實現。

實現思路

方案一

       由前端頁面通過某種方式觸發scheme(如用iframe.src),然後Native用某種方法捕獲對應的url觸發事件,然後拿到當前的觸發url,根據定義好的協議,分析當前觸發了那種方法,然後根據定義來執行。

方案二

       仔細回憶一下,WebView有一個方法,叫setWebChromeClient,可以設定WebChromeClient物件,而這個物件中有三個方法,分別是onJsAlert,onJsConfirm,onJsPrompt,當js呼叫window物件的對應的方法,即window.alert,window.confirm,window.prompt,WebChromeClient物件中的三個方法對應的就會被觸發,我們是不是可以利用這個機制,自己做一些處理呢?答案是肯定的。至於js這三個方法的區別,可以詳見w3c JavaScript訊息框。一般來說,我們是不會使用onJsAlert的,為什麼呢?因為js中alert使用的頻率還是非常高的,一旦我們佔用了這個通道,alert的正常使用就會受到影響,而confirm和prompt的使用頻率相對alert來說,則更低一點。那麼到底是選擇confirm還是prompt呢,其實confirm的使用頻率也是不低的,比如你點一個連結下載一個檔案,這時候如果需要彈出一個提示進行確認,點選確認就會下載,點取消便不會下載,類似這種場景還是很多的,因此不能佔用confirm。而prompt則不一樣,在Android中,幾乎不會使用到這個方法,就是用,也會進行自定義,所以我們完全可以使用這個方法。該方法就是彈出一個輸入框,然後讓你輸入,輸入完成後返回輸入框中的內容。因此,佔用prompt是再完美不過了。

到這一步,我們已經找到了JSBridge雙向通訊的一個通道了,接下來就是如何實現的問題了。

原理概述

       JSBridge是Native程式碼與JS程式碼的通訊橋樑。目前的一種統一方案是:H5觸發url scheme->Native捕獲url scheme->原生分析,執行->原生呼叫h5。如下圖:

url scheme介紹:

1.url scheme是一種類似於url的連結,是為了方便app直接互相呼叫設計的

       具體為,可以用系統的OpenURI開啟一個類似於url的連結(可拼入引數),然後系統會進行判斷,如果是系統的url scheme,則開啟系統應用, 否則找看是否有app註冊這種scheme,開啟對應app

       需要注意的是,這種scheme必須原生app註冊後才會生效,如微信的scheme為(weixin://)

2.而本文JSBridge中的url scheme則是仿照上述的形式的一種方式

      具體為,app不會註冊對應的scheme,而是由前端頁面通過某種方式觸發scheme(如用iframe.src),然後Native用某種方法捕獲對應的url觸發事件,然後拿到當前的觸發url,根據定義好的協議,分析當前觸發了那種方法,然後根據定義來執行

實現流程

一、關鍵步驟分析

  • 第一步:設計出一個Native與JS互動的全域性橋物件
  • 第二步:JS如何呼叫Native
  • 第三步:Native如何得知api被呼叫
  • 第四步:分析url-引數和回撥的格
  • 第五步:Native如何呼叫JS
  • 第六步:H5中api方法的註冊以及格式

如下圖:

二、分步實現

1、第一步:設計出一個Native與JS互動的全域性橋物件

我們規定,JS和Native之間的通訊必須通過一個H5全域性物件JSbridge來實現,該物件有如下特點

  • 該物件名為"JSBridge",是H5頁面中全域性物件window的一個屬性
var JSBridge = window.JSBridge || (window.JSBridge = {});
  • 該物件有如下方法

registerHandler( String,Function ), H5呼叫  註冊本地JS方法,註冊後Native可通過JSBridge呼叫。呼叫後會將方法註冊到本地變數messageHandlers 中

callHandler( String,JSON,Function ), H5呼叫  呼叫原生開放的api,呼叫後實際上還是本地通過url scheme觸發。呼叫時會將回調id存放到本地變數responseCallbacks

_handleMessageFromNative( JSON ), Native呼叫  原生呼叫H5頁面註冊的方法,或者通知H5頁面執行回撥方法

  • 如圖

2、第二步:JS如何呼叫Native

在第一步中,我們定義好了全域性橋物件,可以我們是通過它的callHandler方法來呼叫原生的,那麼它內部經歷了一個怎麼樣的過程呢?如下

callHandler函式內部實現過程

在執行callHandler時,內部經歷了以下步驟:

  • (1)判斷是否有回撥函式,如果有,生成一個回撥函式id,並將id和對應回撥新增進入回撥函式集合responseCallbacks
  • (2)通過特定的引數轉換方法,將傳入的資料,方法名一起,拼接成一個url scheme
    ​
    //url scheme的格式如
    //基本有用資訊就是後面的callbackId,handlerName與data
    //原生捕獲到這個scheme後會進行分析
    var uri = CUSTOM_PROTOCOL_SCHEME://API_Name:callbackId/handlerName?data
  • (3)使用內部早就建立好的一個隱藏iframe來觸發scheme
    //建立隱藏iframe過程
    var messagingIframe = document.createElement('iframe');
    messagingIframe.style.display = 'none';
    document.documentElement.appendChild(messagingIframe);
    
    //觸發scheme
    messagingIframe.src = uri;
    注意,正常來說是可以通過window.location.href達到發起網路請求的效果的,但是有一個很嚴重的問題,就是如果我們連續多次修改window.location.href的值,在Native層只能接收到最後一次請求,前面的請求都會被忽略掉。所以JS端發起網路請求的時候,需要使用iframe,這樣就可以避免這個問題。---引自參考來源

3、第三步:Native如何得知api被呼叫

在上一步中,我們已經成功在H5頁面中觸發scheme,那麼Native如何捕獲scheme被觸發呢?

在Android中(WebViewClient裡)

此處也有兩種實現方案,選取其中的一種即可

方案一:通過iframe.src來觸發scheme,native端通過重寫setWebViewClient的shouldoverrideurlloading可以捕獲到url scheme的觸發

方案二:通過window.prompt(uri, "")來觸發,native端通過重寫WebViewClient的onJsPrompt來獲取uri

詳細細節請往下檢視實現示例中“捕獲url scheme並執行方法的程式碼”

4、第四步:分析url-引數和回撥的格式

在前面的步驟中,Native已經接收到了JS呼叫的方法,那麼接下來,原生就應該按照定義好的資料格式來解析資料了,url scheme的格式前面已經提到。Native接收到Url後,可以按照這種格式將回調引數id、api名、引數提取出來,然後按如下步驟進行

  • (1)根據api名,在本地找尋對應的api方法,並且記錄該方法執行完後的回撥函式id
  • (2)根據提取出來的引數,根據定義好的引數進行轉化

    如果是JSON格式需要手動轉換,如果是String格式直接可以使用

  • (3)原生本地執行對應的api功能方法
  • (4)功能執行完畢後,找到這次api呼叫對應的回撥函式id,然後連同需要傳遞的引數資訊,組裝成一個JSON格式的引數

    回撥的JSON格式為:{responseId:回撥id,responseData:回撥資料}   (1)responseId String型 H5頁面中對應需要執行的回撥  函式的id,在H5中生成url scheme時就已經產生  (2)responseData JSON型 Native需要傳遞給H5的回撥資料,是一個JSON格式:{code:(整型,呼叫是否成功,1成功,0失敗),result:具體需要傳遞的結果資訊,可以為任意型別,msg:一些其它值}

  • (5)通過JSBridge通知H5頁面回撥

5、第五步:Native如何呼叫JS

到了這一步,就該Native通過JSBridge呼叫H5的JS方法或者通知H5進行回調了,具體如下

//將回調資訊傳給H5
JSBridge._handleMessageFromNative(messageJSON);	

如上,實際上是通過JSBridge的_handleMessageFromNative傳遞資料給H5,其中的messageJSON資料格式根據兩種不同的型別,有所區別,如下

Native被動通知H5頁面進行回撥

資料格式為:{responseId:回撥id,responseData:回撥資料} 

Native主動呼叫H5方法

資料格式是:{handlerName:api名,data:資料,callbackId:回撥id}

  • handlerName String型 需要呼叫的,h5中開放的api的名稱
  • data JSON型 需要傳遞的資料,固定為JSON格式(因為我們固定H5中註冊的方法接收的第一個引數必須是JSON,第二個是回撥函式)
  • callbackId String型 原生生成的回撥函式id,h5執行完畢後通過url scheme通知原生api成功執行,並傳遞引數

注意,這一步中,如果Native呼叫的api是h5沒有註冊的,h5頁面上會有對應的錯誤提示。

另外,H5呼叫Native時,Native處理完畢後一定要及時通知H5進行回撥,要不然這個回撥函式不會自動銷燬,多了後會引發記憶體洩漏。

6、第六步:H5中api方法的註冊以及格式

前面有提到Native主動呼叫H5中註冊的api方法,那麼h5中怎麼註冊供原生呼叫的api方法呢?格式又是什麼呢?如下

H5中註冊供原生呼叫的API

//註冊一個測試函式
JSBridge.registerHandler('testH5Func',function(data,callback){
	alert('測試函式接收到資料:'+JSON.stringify(data));
	callback&&callback('{"result":"回撥成功"}');
});

如上述程式碼為註冊一個供原生呼叫的api

H5中註冊的API格式注意

如上程式碼,註冊的api引數是(data,callback)

其中第一個data即原生傳過來的資料,第二個callback是內部封裝過一次的,執行callback後會觸發url scheme,通知原生獲取回撥資訊

實現示例

一、JS實現部分

核心類JSBridge.js

(function() {
	(function() {
		var hasOwnProperty = Object.prototype.hasOwnProperty;
		var JSBridge = window.JSBridge || (window.JSBridge = {});
		//jsbridge協議定義的名稱
		var CUSTOM_PROTOCOL_SCHEME = 'tolvgx';
		//最外層的api名稱
		var API_Name = 'tolvgx_bridge';
		//進行url scheme傳值的iframe
		var messagingIframe = document.createElement('iframe');
		messagingIframe.style.display = 'none';
		messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + API_Name;
		document.documentElement.appendChild(messagingIframe);

		//定義的回撥函式集合,在原生呼叫完對應的方法後,會執行對應的回撥函式id
		var responseCallbacks = {};
		//唯一id,用來確保每一個回撥函式的唯一性
		var uniqueId = 1;
		//本地註冊的方法集合,原生只能呼叫本地註冊的方法,否則會提示錯誤
		var messageHandlers = {};
		//當原生呼叫H5註冊的方法時,通過回撥來呼叫(也就是變為了非同步執行,加強安全性)
		var dispatchMessagesWithTimeoutSafety = true;
		//本地執行中的方法佇列
		var sendMessageQueue = [];

		//實際暴露給原生呼叫的物件
		var Inner = {
			/**
			 * @description 註冊本地JS方法通過JSBridge給原生呼叫
			 * 我們規定,原生必須通過JSBridge來呼叫H5的方法
			 * 注意,這裡一般對本地函式有一些要求,要求第一個引數是data,第二個引數是callback
			 * @param {String} handlerName 方法名
			 * @param {Function} handler 對應的方法
			 */
			registerHandler: function(handlerName, handler) {
				messageHandlers[handlerName] = handler;
			},
			/**
			 * @description 呼叫原生開放的方法
			 * @param {String} handlerName 方法名
			 * @param {JSON} data 引數
			 * @param {Function} callback 回撥函式
			 */
			callHandler: function(handlerName, data, callback) {
				//如果沒有 data
				if(arguments.length == 3 && typeof data == 'function') {
					callback = data;
					data = null;
				}
				_doSend({
					handlerName: handlerName,
					data: data
				}, callback);
			},
			/**
			 * iOS專用
			 * @description 當本地呼叫了callHandler之後,實際是呼叫了通用的scheme,通知原生
			 * 然後原生通過呼叫這個方法來獲知當前正在呼叫的方法佇列
			 */
			_fetchQueue: function() {
				var messageQueueString = JSON.stringify(sendMessageQueue);
				sendMessageQueue = [];
				return messageQueueString;
			},
			/**
			 * @description 原生呼叫H5頁面註冊的方法,或者呼叫回撥方法
			 * @param {String} messageJSON 對應的方法的詳情,需要手動轉為json
			 */
			_handleMessageFromNative: function(messageJSON) {
				setTimeout(_doDispatchMessageFromNative);
				/**
				 * @description 處理原生過來的方法
				 */
				function _doDispatchMessageFromNative() {
					var message;
					try {
						message = JSON.parse(messageJSON);
					} catch(e) {
						//TODO handle the exception
						console.error("原生呼叫H5方法出錯,傳入引數錯誤");
						return;
					}

					//回撥函式
					var responseCallback;
					if(message.responseId) {
						//這裡規定,原生執行方法完畢後準備通知h5執行回撥時,回撥函式id是responseId
						responseCallback = responseCallbacks[message.responseId];
						if(!responseCallback) {
							return;
						}
						//執行本地的回撥函式
						responseCallback(message.responseData);
						delete responseCallbacks[message.responseId];
					} else {
						//否則,代表原生主動執行h5本地的函式
						if(message.callbackId) {
							//先判斷是否需要本地H5執行回撥函式
							//如果需要本地函式執行回撥通知原生,那麼在本地註冊回撥函式,然後再呼叫原生
							//回撥資料有h5函式執行完畢後傳入
							var callbackResponseId = message.callbackId;
							responseCallback = function(responseData) {
								//預設是呼叫EJS api上面的函式
								//然後接下來原生知道scheme被呼叫後主動獲取這個資訊
								//所以原生這時候應該會進行判斷,判斷對於函式是否成功執行,並接收資料
								//這時候通訊完畢(由於h5不會對回撥添加回調,所以接下來沒有通訊了)
								_doSend({
									handlerName: message.handlerName,
									responseId: callbackResponseId,
									responseData: responseData
								});
							};
						}

						//從本地註冊的函式中獲取
						var handler = messageHandlers[message.handlerName];
						if(!handler) {
							//本地沒有註冊這個函式
						} else {
							//執行本地函式,按照要求傳入資料和回撥
							handler(message.data, responseCallback);
						}
					}
				}
			}

		};
		/**
		 * @description JS呼叫原生方法前,會先send到這裡進行處理
		 * @param {JSON} message 呼叫的方法詳情,包括方法名,引數
		 * @param {Function} responseCallback 呼叫完方法後的回撥
		 */
		function _doSend(message, responseCallback) {
			if(responseCallback) {
				//取到一個唯一的callbackid
				var callbackId = Util.getCallbackId();
				//回撥函式新增到集合中
				responseCallbacks[callbackId] = responseCallback;
				//方法的詳情添加回調函式的關鍵標識
				message['callbackId'] = callbackId;
			}
			var uri;
			//android中,可以通過onJsPrompt或者擷取Url訪問都行
			var ua = navigator.userAgent;
			if(ua.match(/(iPhone\sOS)\s([\d_]+)/)||ua.match(/(iPad).*OS\s([\d_]+)/)) {
				//ios中,通過擷取客戶端url訪問
				//因為ios可以不暴露scheme,而是由原生手動獲取
				//正在呼叫的方法詳情新增進入訊息佇列中,原生會主動獲取
				sendMessageQueue.push(message);
				uri = Util.getUri();
			}else{
				//android中相容處理,將所有的引數一起拼接到url中
				uri = Util.getUri(message);
			}
			//獲取 觸發方法的url scheme
			//採用iframe跳轉scheme的方法
			messagingIframe.src = uri;
		}

		var Util = {
			getCallbackId: function() {
				//如果無法解析埠,可以換為Math.floor(Math.random() * (1 << 30));
				// return 'cb_' + (uniqueId++) + '_' + new Date().getTime();
				return Math.floor(Math.random() * (1 << 30));
			},
			//獲取url scheme
			//第二個引數是相容android中的做法
			//android中由於原生不能獲取JS函式的返回值,所以得通過協議傳輸
			getUri: function(message) {
				var uri = CUSTOM_PROTOCOL_SCHEME + '://' + API_Name;
				if(message) {
					//回撥id作為埠存在
					var callbackId, method, params;
					if(message.callbackId) {
						//第一種:h5主動呼叫原生
						callbackId = message.callbackId;
						method = message.handlerName;
						params = message.data;
					} else if(message.responseId) {
						//第二種:原生呼叫h5後,h5回撥
						//這種情況下需要原生自行分析傳過去的port是否是它定義的回撥
						callbackId = message.responseId;
						method = message.handlerName;
						params = message.responseData;
					}
					//引數轉為字串
					params = this.getParam(params);
					//uri 補充
					uri += ':' + callbackId + '/' + method + '?' + params;
				}

				return uri;
			},
			getParam: function(obj) {
				if(obj && typeof obj === 'object') {
					return JSON.stringify(obj);
				} else {
					return obj || '';
				}
			}
		};
		for(var key in Inner) {
			if(!hasOwnProperty.call(JSBridge, key)) {
				JSBridge[key] = Inner[key];
			}
		}

	})();

	//註冊一個測試函式
	JSBridge.registerHandler('testH5Func', function(data, callback) {
		alert('測試函式接收到資料:' + JSON.stringify(data));
		callback && callback('測試回傳資料...');
	});
})();			

測試介面index.html

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>JSBridge</title>
        <meta name="viewport" content="width=device-width, initial-scale=1 user-scalable=0">
        <script type="text/javascript" src="JSBridge.js"></script>
    </head>
    <body>
        <button type="button" onclick="click1()">H5呼叫原生方法</button>
        <br/>
        <br/>
        <button type="button" onclick="click2()">H5呼叫原生方法並回調</button>
    </body>
    
    <script>
        function click1(){
            JSBridge.callHandler('testFormH5',{type:'fromH5'},function(res){})
        }
        function click2(){
            JSBridge.callHandler('testFormH5AndBack',{type:'fromH5AndBack'},function(res){
                alert(res.formNative);
            })
        }
    </script>
</html>

二、Android實現部分

1. JSBridge類實現,這個類的作用是原生定義一些暴露的api。

public class JSBridge {
    private static Map<String, HashMap<String, Method>> exposedMethods = new HashMap<>();

    public static void register(String exposedName, Class<BridgeImpl> clazz) {
        if (!exposedMethods.containsKey(exposedName)) {
            try {
                exposedMethods.put(exposedName, getAllMethod(clazz));
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    private static HashMap<String, Method> getAllMethod(Class injectedCls) throws Exception {
        HashMap<String, Method> mMethodsMap = new HashMap<>();
        Method[] methods = injectedCls.getDeclaredMethods();
        for (Method method : methods) {
            String name;
            if (method.getModifiers() != (Modifier.PUBLIC | Modifier.STATIC) || (name = method.getName()) == null) {
                continue;
            }
            Class[] parameters = method.getParameterTypes();
            if (null != parameters && parameters.length == 3) {
                if (parameters[0] == WebView.class && parameters[1] == JSONObject.class && parameters[2] == Callback.class) {
                    mMethodsMap.put(name, method);
                }
            }
        }
        return mMethodsMap;
    }


    public static String callJava(WebView webView, String uriString) {
        String methodName = "";
        String className = "";
        String param = "{}";
        String port = "";

        if (!TextUtils.isEmpty(uriString) && uriString.startsWith("tolvgx")) {
            Uri uri = Uri.parse(uriString);
            className = uri.getHost();
            param = uri.getQuery();
            port = uri.getPort() + "";
            String path = uri.getPath();
            if (!TextUtils.isEmpty(path)) {
                methodName = path.replace("/", "");
            }
        }
        Log.i("js_bridge_uri:---", uriString+"");

        if (exposedMethods.containsKey(className)) {
            HashMap<String, Method> methodHashMap = exposedMethods.get(className);

            if (methodHashMap != null && methodHashMap.size() != 0 && methodHashMap.containsKey(methodName)) {
                Method method = methodHashMap.get(methodName);
                if (method != null) {
                    try {
                        method.invoke(null, webView, new JSONObject(param), new Callback(webView, port));
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        }
        return null;
    }
}

  注意:其中39行的"tolvgx"需要與JS端的CUSTOM_PROTOCOL_SCHEME一致!

2. Callback類實現,這個類的作用是定義原生中的回撥函式。

public class Callback {
    private static Handler mHandler = new Handler(Looper.getMainLooper());
    private static final String CALLBACK_JS_FORMAT = "javascript:JSBridge._handleMessageFromNative('%s');";
    private String mPort;
    private WeakReference<WebView> mWebViewRef;

    //Java被呼叫使用
    public Callback(WebView view, String port) {
        mWebViewRef = new WeakReference<>(view);
        mPort = port;
    }

    //Java主動呼叫js使用
    public Callback(WebView view) {
        mWebViewRef = new WeakReference<>(view);
    }


    public void apply(JSONObject jsonObject) {
        final String execJs = String.format(CALLBACK_JS_FORMAT, String.valueOf(jsonObject));
        if (mWebViewRef != null && mWebViewRef.get() != null) {
            mHandler.post(new Runnable() {
                @Override
                public void run() {
                    mWebViewRef.get().loadUrl(execJs);
                }
            });
        }
    }

    public String getPort(){
        return mPort;
    }
}

注意:其中第3行的_handleMessageFromNative('%s')對應JS端的_handleMessageFromNative(messageJSON)方法

3. Webview容器關鍵程式碼實現

註冊api方法

//定義api集合
JSBridge.register("tolvgx_bridge",BridgeImpl.class);	

注意:此處的"tolvgx_bridge"需要和JS端的API_Name一致。

捕獲url scheme並執行方法的程式碼

和JS對應,此處也有兩種實現方案,選取其中的一種即可

方案一:如果JS端通過iframe.src來觸發scheme,則

public boolean shouldOverrideUrlLoading(WebView view, String url){
    if(url.contains("tolvgx://")){
        //讀取到url後通過callJava分析呼叫
        JSBridge.callJava(view, url);
    } else {
        // 當有新連線時,使用當前的 WebView
        view.loadUrl(url);
    }
	
    //如果返回false,則WebView處理連結url,如果返回true,代表WebView根據程式來執行url
    return true;
}

注意:第二行的"tolvgx://"需要和JSBridge中39行的"tolvgx"匹配。

方案二:如果JS端通過window.prompt(uri, "")來觸發,則

setWebChromeClient(new WebChromeClient() {
    @Override
    public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
        result.confirm(JSBridge.callJava(view, message));
        return true;
    }
}

4. API 類實現

這個類是一些api的具體實現,webview裡面就是註冊了這些對應的api

JS呼叫Native

如果js主動呼叫app, (1)需要回調,則app需返回responseId (2)無需回撥, 則app可不返回responseId

/**
 * 針對資料的詳細解析:
 *           JSONObject object = new JSONObject();
 *           JSONObject data = new JSONObject();
 *           object.put("responseId", callback.getPort());
 *           object.put("callbackId", "xxx");
 *           object.put("handlerName", "xxx");
 *           object.put("responseData", data);
 *
 *     1.如果js主動呼叫app, 1.需要回調,則app需返回responseId 2.無需回撥, 則app可不返回responseId
 *     2.如果app主動呼叫js, 不要返回responseId, **.需要回調, 則app需js返回callbackId
 *                                           **.無需回撥, 則js可不返回callbackId
 *                                           **.handlerName是app主動呼叫js方法時和js約定好的方法
 *                                           **.其中callbackId為客戶端隨機生成
 */

public class BridgeImpl  {
    /*
        h5呼叫原生方法,不回撥
     */
    public static void testFormH5(WebView webView, JSONObject param, final Callback callback) {
        String type = param.optString("type");
        BrowserActivity activity = (BrowserActivity) webView.getContext();

        Log.d("testFormH5", "type: "+type);

        switch (type) {
            case "fromH5":
                Toast.makeText(activity, type, Toast.LENGTH_LONG).show();

                break;
        }
    }

    /*
        h5呼叫原生方法,並回調
     */
    public static void testFormH5AndBack(WebView webView, JSONObject param, final Callback callback) {
        String type = param.optString("type");
        BrowserActivity activity = (BrowserActivity) webView.getContext();

        Log.d("testFormH5AndBack", "type: "+type);

        try {
            JSONObject data = new JSONObject();
            switch (type) {
                case "fromH5AndBack":
                    Toast.makeText(activity, type, Toast.LENGTH_LONG).show();

                    data.put("formNative", "回撥成功");

                    break;
            }
            if (null != callback) {
                JSONObject object = new JSONObject();
                object.put("responseId", callback.getPort());
                object.put("responseData", data);

                callback.apply(object);
            }

        } catch (Exception e){
            e.printStackTrace();
        }
    }

    /*
        原生呼叫h5後回撥的原生方法
     */
    public static void testH5Func(WebView webView, JSONObject param, final Callback callback) {
        String result = param.optString("result");
        BrowserActivity activity = (BrowserActivity) webView.getContext();

        Log.d("testH5Func", result+"");

        Toast.makeText(activity, result+"", Toast.LENGTH_LONG).show();
    }
}

Native呼叫JS

如果app主動呼叫js,不要返回responseId,**.需要回調,則app需要js返回callbackId
                                                                      **.無需回撥,則js可不返回callbackId
                                                                      **.handlerName是app主動呼叫js方法時和js約定好的方法
                                                                      **.其中callbackId為客戶端隨機生成

(1)無需回撥,如下:

JSONObject data = new JSONObject();
try {
    data.put("fromNative", "不回撥");
    Callback callback = new Callback(mWebView);
    JSONObject object = new JSONObject();
    object.put("handlerName", "testH5Func");
    object.put("data", data);
    callback.apply(object);
} catch (JSONException e) {
    e.printStackTrace();
}

(2)需要回調,如下:

JSONObject data = new JSONObject();
try {
    data.put("fromNative", "回撥");
    Callback callback = new Callback(mWebView);
    JSONObject object = new JSONObject();
    object.put("handlerName", "testH5Func");
    object.put("data", data);
    object.put("callbackId", getCallbackId());
    callback.apply(object);
} catch (JSONException e) {
    e.printStackTrace();
}

注意:回撥方法和呼叫的h5方法名稱一致,在BridgeImpl類中實現。

至此,我們已經把JSBridge的原理和實現示例全部講完,希望對您有所幫助,謝謝。

按照慣例,上原始碼。

相關推薦

Android JSBridge原理實現

在Android中,JSBridge已經不是什麼新鮮的事物了,各家的實現方式也略有差異。大多數人都知道WebView存在一個漏洞,見WebView中介面隱患與手機掛馬利用,雖然該漏洞已經在Android 4.2上修復了,即使用@JavascriptInterfa

JSBridge原理實現

為什麼要用 JSBridge 顧名思義,JSBridge是js和Native之間通訊的橋樑。 Android4.2以下,addJavascriptInterface方式有安全漏洞。 url scheme互動方式是一套現有的成熟方案,可以完美相容各種版本,便於拓展,無重大

Java 線程池的原理實現

控制 try 所在 使用 urn str waiting media .info 這幾天主要是狂看源程序,在彌補了一些曾經知識空白的同一時候,也學會了不少新的知識(比方 NIO)。或者稱為新技術吧。 線程池就是當中之中的一個,一提到線程。我們會想到曾經《操作系統》的

防盜鏈的基本原理實現

rec eal limit ole 站點 new exceptio stub text 1. 我的實現防盜鏈的做法,也是參考該位前輩的文章。基本原理就是就是一句話:通過判斷request請求頭的refer是否來源於本站。(當然請求頭是來自於客戶端的,是可偽造的,暫不在本文

最小二乘法多項式曲線擬合原理實現 zz

博客 del p s 並且 多項式 聯網 python mar 程序 概念 最小二乘法多項式曲線擬合,根據給定的m個點,並不要求這條曲線精確地經過這些點,而是曲線y=f(x)的近似曲線y= φ(x)。 原理 [原理部分由個人根據互聯網上的資料進行總結,希望對大

無限極分類原理實現(轉)

轉換 完成 外灘 獲得 意思 容易 set 導航 另一個   前言   無限極分類是我很久前學到知識,今天在做一個項目時,發現對其概念有點模糊,所以今天就來說說無限極分類。   首先來說說什麽是無限極分類。按照我的理解,就是對數據完成多次分類,如同一棵樹一樣,從根開始,

java監聽器的原理實現

來看 class copyto 圖片 http size stat 順序 方法 監聽器模型涉及以下三個對象,模型圖如下: (1)事件:用戶對組件的一個操作,稱之為一個事件 (2)事件源:發生事件的組件就是事件源 (3)事件監聽器(處理器):監聽並負責處理事件的方法 執行順序

Redis實現分布式鎖原理實現分析

數據表 防止 中一 csdn 訂單 not 產生 www 整體 一、關於分布式鎖 關於分布式鎖,可能絕大部分人都會或多或少涉及到。 我舉二個例子: 場景一:從前端界面發起一筆支付請求,如果前端沒有做防重處理,那麽可能在某一個時刻會有二筆一樣的單子同時到達系統後臺。 場

優先隊列原理實現

() 通過 size 大堆 默認 深入理解 -s 示例 完整 轉自:https://www.cnblogs.com/luoxn28/p/5616101.html 優先隊列是一種用來維護一組元素構成的結合S的數據結構,其中每個元素都有一個關鍵字key,元素之間的比較都是通過k

LVM原理實現過程

LVM原理與實現過程一、什麽是LVM 不管是使用傳統的MBR分區方式或者是GPT的分區方式,在最後數據量逐漸變大的過程中都會出現空間不足的情況,但是若是使用將此分區的數據全部遷移至一個更大空間的磁盤上的遷移時間也是不可想象的,為了解決這個問題,LVM就誕生了。LVM(Logical volume Manag

MapReduce原理實現

讀取 提交 hdf 撲克 datanode 分配 去掉 是否 跟著 課程鏈接:Hadoop大數據平臺架構與實踐--基礎篇 1.MapReduce原理 分而治之,一個大任務分成多個小的子任務(map),並行執行後,合並結果(reduce) 問題1:1000副撲克牌少哪一張牌(

單點登錄原理實現

授權 速度 restful contain ppi 靠譜 遠的 except 令牌 單點登錄原理與實現      關於單點登錄,在項目中用到的是對於cookie中設置的domain 為二級域名,這樣二級域名下的cookie都可以共享,將sessionId存儲在cookie中

數據加密--詳解 RSA加密算法 原理實現

pri mir 對稱加密 模運算 速度 探討 進制 成績 分析 RSA算法簡介 RSA是最流行的非對稱加密算法之一。也被稱為公鑰加密。它是由羅納德·李維斯特(Ron Rivest)、阿迪·薩莫爾(Adi Shamir)和倫納德·阿德曼(Leonard Adleman)在19

線上防雪崩利器——熔斷器設計原理實現

data 沒有 保障系統 狀態模式 熔斷器 data- 雪崩 form cimage 前言 這是一篇根據工作中遇到的問題總結出的最佳實踐。 上周六,我負責的業務在淩晨00-04點的支付全部失敗了。 結果一查,MD,晚上銀行維護,下遊支付系統沒有掛維護公告,在此期間一直請求維

分頁技術原理實現之分頁的意義及方法(一)

轉載自https://www.jb51.net/article/86326.htm。 什麼是分頁技術  分頁,是一種將所有資料分段展示給使用者的技術.使用者每次看到的不是全部資料,而是其中的一部分,如果在其中沒有找到自習自己想要的內容,使用者可以通過制定頁碼或是翻頁的方式轉換可見內容,

Android系統硬體抽象層原理實現之WIFI

http://m.blog.csdn.net/linux_zkf/article/details/7492720 整個WIFIHAL實現都很簡單,都是對wpa_supplicant的操作和使用,如果需要自己實現 WIFI HAL可以參考wifi.c來實現wifi.h中所定義的

推薦系統-協同過濾原理實現

一、基本介紹 1. 推薦系統任務 推薦系統的任務就是聯絡使用者和資訊一方面幫助使用者發現對自己有價值的資訊,而另一方面讓資訊能夠展現在對它感興趣的使用者面前從而實現資訊消費者和資訊生產者的雙贏。 2. 與搜尋引擎比較 相同點:幫助使用者快速發現有用資訊的工具 不同點:和搜尋引擎不同的是推薦系統不

離散傅立葉變換(DFT)和快速傅立葉變換(FFT)原理實現

目錄 1、影象變換 2、離散傅立葉變換(Discrete Fourier Transform) 3、DFT性質 4、DFT與數字影象處理 5、FFT-快速傅立葉變換 6、DFT與FFT的演算法實現 1. 影象變換 — —數學領域中有很多種變換,如傅立葉變換、拉普拉斯變

DeepLearning(深度學習)原理實現

經過三年的狂刷理論,覺得是時候停下來做些有用的東西了,因此決定開博把他們寫下來,一是為了整理學過的理論,二是監督自己並和大家分享。先從DeepLearning談起吧,因為這個有一定的實用性(大家口頭傳的“和錢靠的很近”大笑),國內各個大牛也都談了不少,我儘量從其他方面解釋一下。

什麼是單點登入(原理實現簡介)

單系統登入機制 1、http無狀態協議   web應用採用browser/server架構,http作為通訊協議。http是無狀態協議,瀏覽器的每一次請求,伺服器會獨立處理,不與之前或之後的請求產生關聯,這個過程用下圖說明,三次請求/響應對之間沒有任何聯絡。   但這也同時意味著,任何使用者都能通過