不多在网络上写文章,这一系列文章就是借助掘金这个平台来记录一些东西,到时候本身想看的时候容易找到;惋惜掘金不能设置访问权限 :( 若是有错误,你们请自行百度正确答案,谢谢!!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);
});
}
};
})(); 复制代码