調研對象:
支付寶,微信,雲之家javascript
調研文檔:
Android中JS與Java的極簡交互庫 SimpleJavaJsBridge前端
閱讀類型的業務功能頁面須要由前端H5實現,須要作到服務端可控;java
頁面界面更改減小從新發布新版本的頻率;android
功能頁面部分原型需求沒法實現,須要原生功能支持;web
對將來業務功能的拓展,方便迭代;瀏覽器
定製化JSBridge其實是拓展NativeApp的hybrid程度, 參照微信和支付寶,可打造APP強力的生態圈;安全
jsBridge在支付,錢包,媒體拓展,圖片處理,活動頁面,用戶地理位置網絡狀態都能獲得原生強有力支持;微信
對於閱讀性頁面有更多拓展;網絡
前端和Native對對方的細節知道的越少越好,減小耦合度,暴露的接口儘可能控制在5個之內;數據結構
js與Native之間的通訊,最好定義一套通訊協議或者規則,減小js代碼爲兼容不一樣系統而過多if;
主動發送消息給對方時,對方儘可能對該消息進行反饋,即便無需求對某些功能作反饋,減小if判斷的兼容代碼;
使用前端暴露在window下的一個方法或者一個對象的方法;_handlerFromApp(message)
JSBridge._handlerFromApp(message)
方法名: handlerFromApp
參數:
message: { cbId : "cb_(:id)_(:timeStamp)", //回調函數的id status: 0, //狀態數據 (0:失敗, 1:成功) msg : "ok", //反饋的消息 data : { //... //一些處理後的數據 } }
如下提供的部分參考方法
未對其進行真實測試,由於我使用的是iframe的方法,但原理幾乎相同
建議封裝後提供給Native開發工程師放入對應的APP包中,在webView讀取頁面的時候用對應的Native語言注入頁面,避免頁面在前端導入被抓取;
var doc = JSBridge || window; var uniqueId = 1; var invokeCBMap = {}; var listenCBMap = {}; // function _send(type, funcName, data, cb) { var _id = 'cb_' + (uniqueId++) + '_' + new Date().getTime(); data.cbId = _id; if (type == 'invoke') invokeCBMap[_id] = cb; else if (type == 'listen') listenCBMap[_id] = cb; doc[type](funcName, data); } doc._handlerFromApp = function(msg) { var _id = msg.cbId, callback; if (_id) { callback = invokeCBMap[_id] || listenCBMap[_id]; if (callback) { delete msg.cbId; callback(msg.data); delete invokeCBMap[_id]; } else { console.error('不存在該回調方法'); } } }
如下只介紹前兩個方法,第三個和第二個比較相似
A. Native暴露一個含有通訊方法的類給web調用
B. Native攔截iframe請求
C. Native攔截prompt彈出框
iOS : 可以使用javascriptCore
Android: 直接使用WebView的addJavascriptInterface方法
將一個js對象綁定到一個Native類,在類中實現相應的函數,當js須要調用Native的方法時,只須要直接在js中經過綁定的對象調用相應的函數
肯定對象名稱: (:AppName)JSBridge
Native提供的對象含有的方法:
invoke(funcName, data)
listen(funcName, data)
invoke
:用於web頁面調用Native私有方法的通用方法
參數: funcName
, data
funcName
:對應爲Native內部私有方法的方法名或映射data
:web傳遞給Native的必要數據data
數據結構以下:
{ cbId : "cb_(:id)_(:timeStamp)", //回調函數的id msg : {} //提供給使用方法執行的一些參數 } /** //1.拿wx參考爲例 wx.previewImg({ current: 'http://xxx_1.png', urls : [ 'http: //xxx_0.png', 'http: //xxx_1.png', 'http: //xxx_2.png', 'http: //xxx_3.png', ] }); //2.由於wx對jsbridge進行了一次封裝,jssdk, 而咱們在未封裝時應該以下使用 JSBridge.invoke('imagePreview', { cbId : "cb_(:id)_(:timeStamp)", msg : { current: 'http://xxx_1.png', urls : [ 'http: //xxx_0.png', 'http: //xxx_1.png', 'http: //xxx_2.png', 'http: //xxx_3.png', ] } }); */
那麼當調用以後,Native執行完成對應的私有方法後,執行一次咱們提供的回調接口,如下是javascript的語法,請Native開發工程師對應修改
JSBridge.handlerFromApp({ cbId : "cb_(:id)_(:timeStamp)", //web傳給Native的cbId status: 1, //狀態數據 (0:失敗, 1:成功) msg : "預覽成功", data : {} });
listen
是一個用於web頁面監聽Native方法實現的通用方法
使用環境: 不屬於web頁面上的操做。當用戶直接操做Native上的功能來影響或發送數據給web,或者操做的功能須要用到web頁面上的數據,咱們須要告知Native咱們但願能收到回調;
例子:
微信監聽分享操做
分享的內容是web上的內容(標題,描述,圖片);
獲取分享操做是否完成和分享操做的數據收集;
分享按鈕是原生APP提供;
數據結構和操做與invoke
類似,對應Native開發哥們接收到listen操做後須要存儲一個映射,在被監聽的操做實現上判斷是否是須要執行web端提供的回調接口;
注意:
有關java
addJavascriptInterface
的使用有漏洞,詳情見參考第二條連接,未驗證,僅供讀者自行權衡;
因爲Native App能夠監聽webview的請求,因此web端經過建立一個隱藏的iframe,請求商定後的統一協議來發送數據給Native App;
function createIframeCall(url) { setTimeout(function() { 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() { document.body.removeChild(iframe); }, 100); }, 0); }
url
格式:
(:scheme)://register_type?func=(:funcName)&cbId=(:cbId)&data={...}&verifyTimeStamp=(:new Date().getTime())
scheme
:協議,可用appName,兩端商定,例如weixin,alipayjsbridge
register_type
: 註冊形式,即invoke
仍是listen
funcName
: Native內的方法名或映射
cbId
:見上文
data
:詳細數據
verifyTimeStamp
:驗證的時間參數,沒必要須
;(function() { if (window.ZaihuJSBridge) return; var CUSTOM_PROTOCOL_SCHEME = 'zaihu'; var REGISTER_INVOKE = 'invoke'; var REGISTER_LISTEN = 'listen'; var uniqueId = 1; var invokeCbMap = {}; var listenCbMap = {}; function dataHandler(type, funcName, data, cb) { var register_type = ''; switch (type) { case 'invoke': register_type = REGISTER_INVOKE;break; case 'listen': register_type = REGISTER_LISTEN;break; default: break; } var cbId = ''; if (cb) { cbId = 'cb_' + (uniqueId++) + '_' + new Date().getTime(); invokeCBMap[cbId] = cb; } var dataStr = ''; if (data) dataStr = encodeURIComponent(JSON.stringify(data)); var paramStr = CUSTOM_PROTOCOL_SCHEME + '://' + register_type + '?func=' + funcName + (cbId ? ('&cbId=' + cbId): '') + (data ? ('&data=' + dataStr): ''); createIframeCall(paramStr); } function _invoke(nativeFuncName, data, cb) { dataHandler('invoke', nativeFuncName, data, cb); } function _listen(h5FuncName, data, cb) { dataHandler('listen', h5FuncName, data, cb); } function _handlerFromZaihu(msg) { var data = JSON.parse(msg); var cbId = data.cbId; var cb = invokeCBMap[cbId] || listenCBMap[cbId]; if (cb) { delete data.cbId && cb(data) && delete invokeCBMap[cbId]; } } var app; app = { version: '0.1', invoke: _invoke, on: _listen, log: _log, author: '伊吾魚O(∩_V)O', // private _handlerFromApp: _handlerFromApp }; window.JSBridge = app; })()
須要Native開發兄弟在webview開啓時候爲頁面注入jsbridge.js代碼並執行(防止被前端瀏覽器直接查看源代碼瞭解app的代碼邏輯)
獲取參數執行對應的功能後,執行回調
1.app打開webview
2.loadUrl(頁面url)
3.監聽webview開始,並執行一段js代碼將包內的jsbridge.js文件引入頁面中;
web頁面調用請求接口
jsbridge.invoke(funcName, data);(A方法:Native提供,B&C方法: 前端實現);
接口調用原生功能
原生功能完成後執行回調
A:android曝安全漏洞,但相對來講實現簡單,調用方式容易,且傳遞參數,無需前端搭建jsbridge,只須要封裝易用的sdk,App不須要讀取本地靜態js文件;
B: iframe規定協議,規範統一,須要前端實現jsbridge和封裝sdk, iframe經過url的方式,數據統一爲字符串格式,數據量受限制,兩端要轉義字符;
C: prompt在一些安卓設備受系統劫持,監聽prompt兼容性須要測試,也是字符串形式,數據量不受限,須要轉義字符;
還有不少參考頁面未註明,以及文中有問題的地方歡迎提出。
相關參考
iOS中Objective-C與JavaScript之間相互調用的實現(實現了與Android相同的機制)
Android WebView的Js對象注入漏洞解決方案(JSBridge存在的意義)