摘要:一原理篇下面分別介紹和與的底層交互原理在講解原理之前,首先來了解下的組件,先來看一下蘋果官方的介紹上面的意思是說是一個可加載網頁的對象,它有瀏覽記錄功能,且對加載的網頁內容是可編程的。
做過混合開發的很多人都知道Ionic和PhoneGap之類的框架,這些框架在web基礎上包了一層Native,然后通過Bridge技術使得js可以調用視頻、位置、音頻等功能。本文就是介紹這層Bridge的交互原理,通過閱讀本文你可以了解到js與ios及android底層的通訊原理及JSBridge的封裝技術及調試方法。
一、原理篇下面分別介紹IOS和Android與Javascript的底層交互原理
IOS在講解原理之前,首先來了解下iOS的UIWebView組件,先來看一下蘋果官方的介紹:
You can use the UIWebView class to embed web content in your application. To do so, you simply create a UIWebView object, attach it to a window, and send it a request to load web content. You can also use this class to move back and forward in the history of webpages, and you can even set some web content properties programmatically.
上面的意思是說UIWebView是一個可加載網頁的對象,它有瀏覽記錄功能,且對加載的網頁內容是可編程的。說白了UIWebView有類似瀏覽器的功能,我們使用可以它來打開頁面,并做一些定制化的功能,如可以讓js調某個方法可以取到手機的GPS信息。
但需要注意的是,Safari瀏覽器使用的瀏覽器控件和UIwebView組件并不是同一個,兩者在性能上有很大的差距。幸運的是,蘋果發布iOS8的時候,新增了一個WKWebView組件,如果你的APP只考慮支持iOS8及以上版本,那么你就可以使用這個新的瀏覽器控件了。
原生的UIWebView類提供了下面一些屬性和方法
屬性:
loading:是否處于加載中
canGoBack:A Boolean value indicating whether the receiver can move backward. (只讀)
canGoForward:A Boolean value indicating whether the receiver can move forward. (只讀)
request:The URL request identifying the location of the content to load. (read-only)
方法:
loadData:Sets the main page contents, MIME type, content encoding, and base URL.
loadRequest:加載網絡內容
loadHTMLString:加載本地HTML文件
stopLoading:停止加載
goBack:后退
goForward:前進
reload:重新加載
stringByEvaluatingJavaScriptFromString:執行一段js腳本,并且返回執行結果
Native(Objective-C或Swift)調用Javascript方法Native調用Javascript語言,是通過UIWebView組件的stringByEvaluatingJavaScriptFromString方法來實現的,該方法返回js腳本的執行結果。
// Swift webview.stringByEvaluatingJavaScriptFromString("Math.random()") // OC [webView stringByEvaluatingJavaScriptFromString:@"Math.random();"];
從上面代碼可以看出它其實就是調用了window下的一個對象,如果我們要讓native來調用我們js寫的方法,那這個方法就要在window下能訪問到。但從全局考慮,我們只要暴露一個對象如JSBridge對native調用就好了,所以在這里可以對native的代碼做一個簡單的封裝:
//下面為偽代碼 webview.setDataToJs(somedata); webview.setDataToJs = function(data) { webview.stringByEvaluatingJavaScriptFromString("JSBridge.trigger(event, data)") }Javascript調用Native(Objective-C或Swift)方法
反過來,Javascript調用Native,并沒有現成的API可以直接拿來用,而是需要間接地通過一些方法來實現。UIWebView有個特性:在UIWebView內發起的所有網絡請求,都可以通過delegate函數在Native層得到通知。這樣,我們就可以在UIWebView內發起一個自定義的網絡請求,通常是這樣的格式:jsbridge://methodName?param1=value1¶m2=value2
于是在UIWebView的delegate函數中,我們只要發現是jsbridge://開頭的地址,就不進行內容的加載,轉而執行相應的調用邏輯。
發起這樣一個網絡請求有兩種方式:1. 通過localtion.href;2. 通過iframe方式;
通過location.href有個問題,就是如果我們連續多次修改window.location.href的值,在Native層只能接收到最后一次請求,前面的請求都會被忽略掉。
使用iframe方式,以喚起Native APP的分享組件為例,簡單的封閉如下:
var url = "jsbridge://doAction?title=分享標題&desc=分享描述&link=http%3A%2F%2Fwww.baidu.com"; var iframe = document.createElement("iframe"); iframe.style.width = "1px"; iframe.style.height = "1px"; iframe.style.display = "none"; iframe.src = url; document.body.appendChild(iframe); setTimeout(function() { iframe.remove(); }, 100);
然后Webview就可以攔截這個請求,并且解析出相應的方法和參數。如下代碼所示:
func webView(webView: UIWebView, shouldStartLoadWithRequest request: NSURLRequest, navigationType: UIWebViewNavigationType) -> Bool { print("shouldStartLoadWithRequest") let url = request.URL let scheme = url?.scheme let method = url?.host let query = url?.query if url != nil && scheme == "jsbridge" { print("scheme == (scheme)") print("method == (method)") print("query == (query)") switch method! { case "getData": self.getData() case "putData": self.putData() case "gotoWebview": self.gotoWebview() case "gotoNative": self.gotoNative() case "doAction": self.doAction() case "configNative": self.configNative() default: print("default") } return false } else { return true } }Android
在android中,native與js的通訊方式與ios類似,ios中的通過schema方式在android中也是支持的。
javascript調用native方式目前在android中有三種調用native的方式:
1.通過schema方式,使用shouldOverrideUrlLoading方法對url協議進行解析。這種js的調用方式與ios的一樣,使用iframe來調用native代碼。
2.通過在webview頁面里直接注入原生js代碼方式,使用addJavascriptInterface方法來實現。
在android里實現如下:
class JSInterface { @JavascriptInterface //注意這個代碼一定要加上 public String getUserData() { return "UserData"; } } webView.addJavascriptInterface(new JSInterface(), "AndroidJS");
上面的代碼就是在頁面的window對象里注入了AndroidJS對象。在js里可以直接調用
alert(AndroidJS.getUserData()) //UserDate
3.使用prompt,console.log,alert方式,這三個方法對js里是屬性原生的,在android webview這一層是可以重寫這三個方法的。一般我們使用prompt,因為這個在js里使用的不多,用來和native通訊副作用比較少。
class YouzanWebChromeClient extends WebChromeClient { @Override public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) { // 這里就可以對js的prompt進行處理,通過result返回結果 } @Override public boolean onConsoleMessage(ConsoleMessage consoleMessage) { } @Override public boolean onJsAlert(WebView view, String url, String message, JsResult result) { } }Native調用javascript方式
在android里是使用webview的loadUrl進行調用的,如:
// 調用js中的JSBridge.trigger方法 webView.loadUrl("javascript:JSBridge.trigger("webviewReady")");二、庫的封裝 js調用native的封裝
上面我們了解了js與native通訊的底層原理,所以我們可以封裝一個基礎的通訊方法doCall來屏蔽android與ios的差異。
YouzanJsBridge = { doCall: function(functionName, data, callback) { var _this = this; // 解決連續調用問題 if (this.lastCallTime && (Date.now() - this.lastCallTime) < 100) { setTimeout(function() { _this.doCall(functionName, data, callback); }, 100); return; } this.lastCallTime = Date.now(); data = data || {}; if (callback) { $.extend(data, { callback: callback }); } if (UA.isIOS()) { $.each(data, function(key, value) { if ($.isPlainObject(value) || $.isArray(value)) { data[key] = JSON.stringify(value); } }); var url = Args.addParameter("youzanjs://" + functionName, data); var iframe = document.createElement("iframe"); iframe.style.width = "1px"; iframe.style.height = "1px"; iframe.style.display = "none"; iframe.src = url; document.body.appendChild(iframe); setTimeout(function() { iframe.remove(); }, 100); } else if (UA.isAndroid()) { window.androidJS && window.androidJS[functionName] && window.androidJS[functionName](JSON.stringify(data)); } else { console.error("未獲取platform信息,調取api失敗"); } } }
上面android端我們使用了addJavascriptInterface方法來注入一個AndroidJS對象。
項目通用方法抽象在項目的實踐中,我們逐漸抽象出一些通用的方法,這些方法基本上都是可以滿足項目的需求。如下所示:
1.getData(datatype, callback, extra) H5從Native APP獲取數據使用場景:H5需要從Native APP獲取某些數據的時候,可以調用這個方法。
參數 | 類型 | 是否必須 | 示例值 | 說明 |
---|---|---|---|---|
datatype | String | 是 | userInfo | 數據類型 |
callback | Function | 是 | 回調函數 | |
extra | Object | 否 | 傳遞給Native APP的數據對象 |
示例代碼:
JSBridge.getData("userInfo",function(data) { console.log(data); });2.putData(datatype, data) H5告訴Native APP一些數據
使用場景:H5告訴Native APP一些數據,可以調用這個方法。
參數 | 類型 | 是否必須 | 示例值 | 說明 |
---|---|---|---|---|
datatype | String | 是 | userInfo | 數據類型 |
data | Object | 是 | { username: "zhangsan", age: 20 } | 傳遞給Native APP的數據對象 |
示例代碼:
JSBridge.putData("userInfo", { username: "zhangsan", age: 20 });3.gotoWebview(url, page, data) Native APP新開一個Webview窗口,并打開相應網頁
參數 | 類型 | 是否必須 | 示例值 | 說明 |
---|---|---|---|---|
url | String | 是 | http://www.youzan.com | 網頁鏈接地址,一般都只要傳遞URL參數就可以了 |
page | String | 否 | web | 網頁page類型,默認為web |
data | Object | 否 | 額外參數對象 |
示例代碼:
// 示例1:打開一個網頁 JSBridge.gotoWebview("http://www.youzan.com"); // 示例2:打開一個網頁,并且傳遞額外的參數給Native APP JSBridge.gotoWebview("http://www.youzan.com", "goodsDetail", { goods_id: 10000, title: "這是商品的標題", desc: "這是商品的描述" });4.gotoNative(page, data) 從H5頁面跳轉到Native APP的某個原生界面
參數 | 類型 | 是否必須 | 示例值 | 說明 |
---|---|---|---|---|
page | String | 是 | loginPage | Native頁面標示符,例如loginPage |
data | Object | 否 | { username: "zhangsan", age: 20 } | 額外參數對象 |
示例代碼:
// 示例1:打開Native APP登錄頁面 JSBridge.gotoNative("loginPage"); // 示例2:打開Native APP登錄頁面,并且傳遞用戶名給Native APP JSBridge.gotoNative("loginPage", { username: "張三" });5.doAction(action, data) 功能上的一些操作
參數 | 類型 | 是否必須 | 示例值 | 說明 |
---|---|---|---|---|
action | String | 是 | copy | 操作功能類型,例如分享、復制 |
data | Object | 否 | { content: "這是要復制的內容" } | 額外參數 |
示例代碼:
// 示例1:調用Native APP復制一段文本到剪切板 JSBridge.doAction("copy", { content: "這是要復制的內容" }); // 示例2:調用Native APP的分享組件,分享當前網頁到微信 JSBridge.doAction("share", { title: "分享標題", desc: "分享描述", link: "http://www.youzan.com", imgs_url: "http://wap.koudaitong.com/v2/common/url/create?type=homepage&index%2Findex=&kdt_id=63077&alias=63077" });三、調試篇 使用Safari進行UIWebView的調試
(1)首先需要打開Safari的調試模式,在Safari的菜單中,選擇“Safari”→“Preference”→“Advanced”,勾選上“Show Develop menu in menu bar”選項,如下圖所示。
(2)打開真機或iPhone模擬器的調試模式,在真機或iPhone模擬器中打開設置界面,選擇“Safari”→“高級”→“Web檢查器”,選擇開啟即可,如下圖所示。
(3)將真機通過USB連上電腦,或者開啟模擬器,Safari的“Develop”菜單下便會多出相應的菜單項,如圖所示。
(4)Safari連接上UIWebView之后,我們就可以直接在Safari中直接修改HTML、CSS,以及調試Javascript。
四、參考鏈接UIWebView Class Reference
WKWebView Class Reference
https://github.com/marcuswestin/WebViewJavascriptBridge
本文由 @kk @勁風 共同創作,首發于有贊技術博客: http://tech.youzan.com/jsbridge/
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/84341.html
摘要:相關參考中與之間相互調用的實現實現了與相同的機制的對象注入漏洞解決方案存在的意義 前期調研 調研對象:支付寶,微信,云之家 調研文檔:Android中JS與Java的極簡交互庫 SimpleJavaJsBridge 設計需求 閱讀類型的業務功能頁面需要由前端H5實現,需要做到服務端可控; 頁面界面更改減少重新發布新版本的頻率; 功能頁面部分原型需求無法實現,需要原生功能支持; 對未來...
閱讀 2684·2021-10-22 09:55
閱讀 2008·2021-09-27 13:35
閱讀 1267·2021-08-24 10:02
閱讀 1478·2019-08-30 15:55
閱讀 1198·2019-08-30 14:13
閱讀 3471·2019-08-30 13:57
閱讀 1975·2019-08-30 11:07
閱讀 2447·2019-08-29 17:12