不多在網絡上寫文章,這一系列文章就是藉助掘金這個平臺來記錄一些東西,到時候本身想看的時候容易找到;惋惜掘金不能設置訪問權限 :( 若是有錯誤,你們請自行百度正確答案,謝謝!!android
有摘錄,有原創:)ios
H5和原生app(ios,android)交互的載體基本都是基於Webview,能夠把Webview看做是一個性能打八折的移動瀏覽器。web
簡單說下這幾種:WKWebView 、UIWebView、JavaScriptCorechrome
WKWebView:蘋果在ios8以後也引入了專門負責處理網頁視圖的框架WebKit,Webkit是啥,接觸過H五、chrome的確定都知道。chrome使用的也是基於webkit內核的Chromium引擎。WKWebView優勢不少,支持更多H5特性,刷新效率及內置手勢等,更增強大,性能也更優,不一一列舉,若是你們app不須要兼容7及如下版本,不須要攔截一些請求,直接解析本地一些文件,建議使用WKWebView。json
UIWebView:較老webview,第一代。其中stringByEvaluatingJavaScriptFromString方法提供了OC與js交互的能力。小程序
JavaScriptCore(ios7及之後版本)。JavaScriptCore框架是webkit重要組成部分,主要是對JS進行解析和提供執行環境,Javascript的虛擬機,有點相似v8引擎,我本身這麼理解:)正是它爲ios提供了執行JavaScript代碼的能力。ReactNative應該都是經過JavaScriptCore去解析的(本身猜想)。swift
微信小程序的邏輯層也是由JavaScriptCore做爲運行環境。微信小程序
目前兼顧兼容性、比較成熟的方案仍是經過攔截URL的方式。api
UIWebView的特性,在UIWebView內發起的全部網絡請求,均可以在Native層被捕捉到。瀏覽器
利用這一特性,就能夠在UIWebView內發起一個自定義的網絡請求,通常格式:jsbridge://method?參數1=value1&參數2=value2
因而在UIWebView中,只要發現是jsbridge://開頭的url,就不進行內容的加載,而是執行相應的邏輯處理。
嵌入webview的h5中的js通常是經過動態建立隱藏iframe標籤,賦值上文提到的連接給src,iframe不會引發頁面調轉、刷新。
主要代碼:
var src= 'jsbridge://method?參數1=value1&參數2=value2';
var iframe = document.createElement('iframe');
iframe.style.display = 'none';
iframe.src = src;
document.body.appendChild(iframe);
//再刪除iframesetTimeout(function() {
iframe.remove();
}, 50);
複製代碼
Android WebView也是基於WebKit引擎的一個組件,Android的Webview在低版本和高版本採用了不一樣的webkit版本內核,4.4後直接使用了Chrome。
這個組件功能很是強大,除了具備通常View的屬性和設置外,還能夠對url請求、頁面加載、渲染、頁面交互進行強大的處理。
通常經常使用onJsPrompt、prompt進行回調攔截
囉嗦了這麼多,還沒說到主題,JsBridge。一句話,JSBridge是Native代碼與JS代碼的通訊橋樑。
設計一個jsbridge主要分幾大步驟:
第一步:設計出一個Native與JS交互的全局中間對象
第二步:JS如何調用Native
第三步:Native如何得知api被調用
第四步:分析url-參數和回調的格式
第五步:Native如何調用JS
第六步:H5中api方法的註冊以及格式
H5端JS核心代碼(轉載劉貝,固然還有其餘的實現,原理是相同的,如下這段寫的比較明白)
(function() {
(function() {
var hasOwnProperty = Object.prototype.hasOwnProperty;
var JSBridge = window.JSBridge || (window.JSBridge = {});
//jsbridge協議定義的名稱
var CUSTOM_PROTOCOL_SCHEME = 'CustomJSBridge';
//最外層的api名稱
var API_Name = 'namespace_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();
},
//獲取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('測試回傳數據...');
});
/*
***************************API********************************************
* 開放給外界調用的api
* */
window.jsapi = {};
/**
***app 模塊
* 一些特殊操做
*/
jsapi.app = {
/**
* @description 測試函數
*/
testNativeFunc: function() {
//調用一個測試函數
JSBridge.callHandler('testNativeFunc', {}, function(res) {
callback && callback(res);
});
}
};
})(); 複製代碼