做過混合開發的人都知道ionic和phonegap之類的架構,這些架構在web基礎上包裝一層native,然後通過bridge技術的js調用本地的庫。
在講jsbridge技術之前,我們來看一下傳統的實作方式。
native調用js比較簡單,隻要遵循:"javascript: 方法名('參數,需要轉為字元串')"的規則即可。
在4.4之前,調用的方式:
4.4以後(包括4.4),使用以下方式:
說明:
4.4之前native通過loadurl來調用js方法,隻能讓某個js方法執行,但是無法擷取該方法的傳回值
4.4之後,通過evaluatejavascript異步調用js方法,并且能在onreceivevalue中拿到傳回值
不适合傳輸大量資料(大量資料建議用接口方式擷取)
mwebview.loadurl("javascript: 方法名('參數,需要轉為字元串')");函數需在ui線程運作,因為mwebview為ui控件
js調用native需要對webview設定@javascriptinterface注解,這裡有個漏洞,後面會給大家說明。要想js能夠native,需要對webview設定以下屬性。
這裡我們看到了getjsbridge(),native中通過addjavascriptinterface添加暴露出來的js橋對象,然後再該對象内部聲明對應的api方法。
那麼html怎麼調用native的方法呢?
在android4.2以上(api17後),暴露的api要加上注解@javascriptinterface,否則會找不到方法。
在api17以前,addjavascriptinterface有風險,hacker可以通過反編譯擷取native注冊的js對象,然後在頁面通過反射java的内置 靜态類,擷取一些敏感的資訊和破壞
js調用native暴露的api,并且能得到相應傳回值
native調用js的方法比較簡單,native通過stringbyevaluatingjavascriptfromstring調用html綁定在window上的函數。不過應注意oc和swift的寫法。
native調用js方法時,能拿到js方法的傳回值
native中通過引入官方提供的javascriptcore庫(ios7以上),然後可以将api綁定到jscontext上(然後html中js預設通過window.top.*可調用)。
引入官方的庫檔案
native注冊api函數(oc)
html中js調用native方法
ios7才出現這種方式,在這之前,js無法直接調用native,隻能通過jsbridge方式簡介調用
js能調用到已經暴露的api,并且能得到相應傳回值
ios原生本身是無法被js調用的,但是通過引入官方提供的第三方"javascriptcore",即可開放api給js調用
jsbridge:聽其取名就是js和native之前的橋梁,而實際上jsbridge确實是js和native之前的一種通信方式。簡單的說,jsbridge就是定義native和js的通信,native隻通過一個固定的橋對象調用js,js也隻通過固定的橋對象調用native。jsbridge另一個叫法及大家熟知的hybrid app技術。
流程:h5->通過某種方式觸發一個url->native捕獲到url,進行分析->原生做處理->native調用h5的jsbridge對象傳遞回調。
我們前面講過了原生的webview/uiwebview控件已經能夠和js實作資料通信了,那為什麼還要jsbridge呢?
其實使用jsbridge有很多方面的考慮:
android4.2以下,addjavascriptinterface方式有安全漏掉
ios7以下,js無法調用native
url scheme互動方式是一套現有的成熟方案,可以完美相容各種版本,對以前老版本技術的相容。
url scheme是一種類似于url的連結,是為了友善app直接互相調用設計的。具體來講如果是系統的url scheme,則打開系統應用,否則找看是否有app注冊這種scheme,打開對應app。
注:這種scheme必須原生app注冊後才會生效。
而在我們實際的開發中,app不會注冊對應的scheme,而是由前端頁面通過某種方式觸發scheme(如用iframe.src),然後native用某種方法捕獲對應的url觸發事件,然後拿到目前的觸發url,根據定義好的協定,分析目前觸發了那種方法。
要實作jsbridge,我們需要按以下步驟分析:
第一步:設計出一個native與js互動的全局橋對象
第二步:js如何調用native
第三步:native如何得知api被調用
第四步:分析url-參數和回調的格式
第五步:native如何調用js
第六步:h5中api方法的注冊以及格式
jsbridge的完整流程可總結為:
我們規定,js和native之間的通信必須通過一個h5全局對象jsbridge來實作。該對象有如下特點:
該對象名為"jsbridge",是h5頁面中全局對象window的一個屬性,形如:
該對象有如下方法:
registerhandler( string,function )h5調用注冊本地js方法,注冊後native可通過jsbridge調用。調用後會将方法注冊到本地變量messagehandlers 中。
callhandler( string,json,function )h5調用 調用原生開放的api,調用後實際上還是本地通過url scheme觸發。調用時會将回調id存放到本地變量responsecallbacks中
_handlemessagefromnative( json )native調用 原生調用h5頁面注冊的方法,或者通知h5頁面執行回調方法
我們定義好了全局橋對象,可以通過它的callhandler方法來調用原生的api。
在執行callhandler時,内部經曆了以下步驟:
判斷是否有回調函數,如果有,生成一個回調函數id,并将id和對應回調添加進入回調函數集合responsecallbacks中。
通過特定的參數轉換方法,将傳入的資料,方法名一起,拼接成一個url scheme
使用内部早就建立好的一個隐藏iframe來觸發scheme
注:正常來說是可以通過window.location.href達到發起網絡請求的效果的,但是有一個很嚴重的問題,就是如果我們連續多次修改window.location.href的值,在native層隻能接收到最後一次請求,前面的請求都會被忽略掉。是以js端發起網絡請求的時候,需要使用iframe,這樣就可以避免這個問題。
上一步,我們已經成功在h5頁面中觸發scheme,那麼native如何捕獲scheme被觸發呢?
根據系統不同,android和ios分别有自己的處理方式。
在android中(webviewclient裡),通過shouldoverrideurlloading可以捕獲到url scheme的觸發。
ios中,uiwebview有個特性:在uiwebview内發起的所有網絡請求,都可以通過delegate函數在native層得到通知。這樣,我們可以在webview中捕獲url scheme的觸發(原理是利用 shouldstartloadwithrequest)
在前面的步驟中,native已經接收到了js調用的方法,那麼接下來,原生就應該按照定義好的資料格式來解析資料了,native接收到url後,可以按照這種格式将回調參數id、api名、參數提取出來,然後按如下步驟進行。
根據api名,在本地找尋對應的api方法,并且記錄該方法執行完後的回調函數id
根據提取出來的參數,根據定義好的參數進行轉化
原生本地執行對應的api功能方法
功能執行完畢後,找到這次api調用對應的回調函數id,然後連同需要傳遞的參數資訊,組裝成一個json格式的參數
通過jsbridge通知h5頁面回調
到了這一步,就該native通過jsbridge調用h5的js方法或者通知h5進行回調了。其中的messagejson資料格式根據兩種不同的類型。
native通知h5頁面進行回調:
資料格式為: native通知h5回調的json格式。
native主動調用h5方法:
native主動調用h5方法時,資料格式是:{handlername:api名,data:資料,callbackid:回調id}:
handlername string型 需要調用的,h5中開放的api的名稱
data json型 需要傳遞的資料,固定為json格式(因為我們固定h5中注冊的方法接收的第一個參數必須是json,第二個是回調函數)
callbackid string型 原生生成的回調函數id,h5執行完畢後通過url scheme通知原生api成功執行,并傳遞參數
前面有提到native主動調用h5中注冊的api方法,那麼h5中怎麼注冊供原生調用的api方法呢?
如上代碼,其中第一個data即原生傳過來的資料,第二個callback是内部封裝過一次的,執行callback後會觸發url scheme,通知原生擷取回調資訊.
jsbridge對象圖解:
jsbridge實作完整流程:
那麼我們在實際的開發中,如何針對android和ios的不同情況,統一出一種完整的方案。
前面提到的jsbridge都是基于url scheme的,但其實如果不考慮android4.2以下,ios7以下,其實也可以用另一套方案的。
native調用js的方法不變
js調用native是不再通過觸發url scheme,而是采用自帶的互動
具體來講:
android中,原生通過 addjavascriptinterface開放一個統一的api給js調用,然後将觸發url scheme步驟變為調用這個api,其餘步驟不變。
os中,原生通過javascriptcore裡面的方法來注冊一個統一api,其餘和android中一樣。