Hybrid APP 优势
网上查了一圈,没有找到符合我内心的描述,所以就算忽略吧,反正优点很多,这不是我们要说的重点,忽略几百字或者几十字总有的
解决方案--jsbridge
Hybrid网上很多,比较大型的方案还是推荐jsbridge,其最大优势在于方便于扩展,下面文章都是基于jsbridge来说的。
安利两个成熟的库
iOS WebViewJavascriptBridge
Android JsBridge
基本原理
这也不是这篇帖子的重点,上张网上盗来的图,个人看来这个图有些地方有点过期了,回头写jsbridge的原理的时候,再详细来说。
JS核心函数认识
1、获取Native植入对象,后续的调用都是使用的这个对象
/**
* 获取客户端的桥接对象
* @returns {Promise<any>}
*/
setupWebViewJavascriptBridge: function () {
return new Promise(resolve => {
if (window.WebViewJavascriptBridge) {
// 项目正常运行时window上已经挂载了客户端的桥接对象WebViewJavascriptBridge,直接使用对象
return resolve(WebViewJavascriptBridge);
}
if (window.WVJBCallbacks) {
// 已创建了回调队列,直接往回调队列添加回调函数
return window.WVJBCallbacks.push(resolve);
}
// 页面初始化时在客户端注入桥接对象前定义个回调函数队列,客户端在初始化后会依次调用队列中的函数
window.WVJBCallbacks = [resolve];
// 用隐藏的iframe,来触发url scheme,从而触发WebViewJavascriptBridge的注入
var WVJBIframe = document.createElement("iframe");
WVJBIframe.style.display = "none";
WVJBIframe.src = "wvjbscheme://__BRIDGE_LOADED__";
document.documentElement.appendChild(WVJBIframe);
setTimeout(function () {
document.documentElement.removeChild(WVJBIframe);
}, 0);
})
}
2、js调用客户端
/**
* h5调用客户端协议
* @param bridgeOpStr 方法名称
* @param paras 调用协议的参数
* @returns {PromiseLike<any | never> | Promise<any | never>}
*/
callHandler: function (bridgeOpStr, paras) {
return this.setupWebViewJavascriptBridge().then(bridge => {
return new Promise(resolve => {
bridge.callHandler(bridgeOpStr, paras, (response) => {
resolve(response);
});
})
});
}
3、客户端调用js
/**
* 客户端调js
* @param bridgeOpStr 方法名
* @returns {PromiseLike<any | never> | Promise<any | never>}
*/
registerHandler: function (bridgeOpStr) {
return this.setupWebViewJavascriptBridge().then(bridge => {
return new Promise(resolve => {
bridge.registerHandler(bridgeOpStr, (data, responseCallback) => {
// data 传过来的数据, responseCallback 回调函数
resolve(data, responseCallback);
});
})
});
}
封装
散装的js函数足够可以使我们完成项目了,不过为了更好的使用,我们应该将其封装到一个对象中,然后全局使用这个对象,这样做有几个优点。
1、方便调用。
2、因为jsbridge天生异步,所以我们有时候需要借助队列来控制一些逻辑。
3、版本等函数管理
主要封装内容:
- 版本管理,方便APP后续升级,在市场上形成多版本状态。
- 开发版本回调管理,方便开发过程中的调试
- 统一用Promise调用,并且返回结构统一。
算了吹多了没意思,直接上代码
const dev = process.env.NODE_ENV !== 'production'
/**
* 业务函数最终返回的是 promise, 其回调结果都是对象,
* result 状态: 1 成功, 0 失败;
* msg 提示信息。
* data 业务数据。
*/
let bridgefunc = {
// 当前协议版本
version: -1,
/**
* 获取当前协议版本,都支持
* @returns {*}
*/
getAppProtocolVersion() {
let jsonData = {};
jsonData.type = "getAppProtocolVersion";
return this.callHandler("phonebridge", jsonData);
},
// 初始化版本号
initVersion() {
this.getAppProtocolVersion().then(res => {
if (res.result == 1) {
this.version = res.data;
this.consumeNoViersionQueue()
}
})
},
// 没有版本的时候,交互先放入队列等版本回来再消费
noVersionQueue: [],
// 消费没有version队列
consumeNoViersionQueue() {
while (this.noVersionQueue.length) {
let callback = this.noVersionQueue.shift()
callback.call(this, true);
}
},
// 函数版本和测试支持,如果不包含函数对象,表示都支持。
funcSupportVersion: {
scanCode: {
version: 1.0,
devValue: '1234567890' // 开发环境回调值
},
pageWillShow: {
version: 2.5 // 回调场景默认值没意义,立即是不正确的,不写开发环境回到值,就不会触发
}
},
// 检查协议是否支持
checkSupport (bridgeOpStr, resolve, callback) {
if (dev) {
if (this.funcSupportVersion[bridgeOpStr] && this.funcSupportVersion[bridgeOpStr].devValue != undefined) {
resolve({
result: 1,
msg: '这是开发环境回调',
data: this.funcSupportVersion[bridgeOpStr].devValue
})
} else {
resolve({
result: 0,
msg: '开发环境,不支持调用'
})
}
callback(false);
} else if (bridgeOpStr == 'getAppProtocolVersion') {
// 获取version直接继续,否则死循环
callback(true);
} else if (this.version == -1) {
this.initVersion()
// 还没有获取到版本号,加入等待队列
this.noVersionQueue.push(callback)
} else if (!this.funcSupportVersion[bridgeOpStr] || !this.funcSupportVersion[bridgeOpStr].version || this.version >= this.funcSupportVersion[bridgeOpStr].version) {
// 没有设置支持函数支持版本
callback(true);
} else {
resolve({
result: 0,
msg: '当前APP版本过老,不支持该函数,函数名:' + bridgeOpStr
})
callback(false);
}
},
/**
* h5调用客户端协议
* @param bridgeOpStr 方法名称
* @param paras 调用协议的参数
* @returns {PromiseLike<any | never> | Promise<any | never>}
*/
callHandler (bridgeOpStr, paras) {
return this.setupWebViewJavascriptBridge().then(bridge => {
return new Promise(resolve => {
this.checkSupport(bridgeOpStr, resolve, (result) => {
if (result) {
bridge.callHandler(bridgeOpStr, paras, (response) => {
resolve({
result: 1,
data: response
});
});
}
})
})
});
},
/**
* 客户端调js
* @param bridgeOpStr 方法名
* @returns {PromiseLike<any | never> | Promise<any | never>}
*/
registerHandler (bridgeOpStr) {
return this.setupWebViewJavascriptBridge().then(bridge => {
return new Promise(resolve => {
this.checkSupport(bridgeOpStr, resolve, (result) => {
if (result) {
bridge.registerHandler(bridgeOpStr, (data, responseCallback) => {
// data 传过来的数据, responseCallback 回调函数
resolve({
result: 1,
data: data,
responseCallback: responseCallback
});
});
}
})
})
});
},
/**
* 获取客户端的桥接对象
* @returns {Promise<any>}
*/
setupWebViewJavascriptBridge () {
return new Promise(resolve => {
if (window.WebViewJavascriptBridge) {
// 项目正常运行时window上已经挂载了客户端的桥接对象WebViewJavascriptBridge,直接使用对象
return resolve(WebViewJavascriptBridge);
}
if (window.WVJBCallbacks) {
// 已创建了回调队列,直接往回调队列添加回调函数
return window.WVJBCallbacks.push(resolve);
}
// 页面初始化时在客户端注入桥接对象前定义个回调函数队列,客户端在初始化后会依次调用队列中的函数
window.WVJBCallbacks = [resolve];
// 用隐藏的iframe,来触发url scheme,从而触发WebViewJavascriptBridge的注入
var WVJBIframe = document.createElement("iframe");
WVJBIframe.style.display = "none";
WVJBIframe.src = "wvjbscheme://__BRIDGE_LOADED__";
document.documentElement.appendChild(WVJBIframe);
setTimeout(function () {
document.documentElement.removeChild(WVJBIframe);
}, 0);
})
},
/**
* // 扫一扫,版本1.0及之后可以调用
* @returns {*} promise 返回 code
*/
scanCode() {
let jsonData = {};
return this.callHandler("scanCode", jsonData);
},
/**
* 注册回调函数,页面从其他地方回来了,版本2.5及之后可以调用
* @returns {*}
*/
pageWillShow() {
let jsonData = {};
return this.registerHandler("pageWillShow", jsonData);
}
};
export default bridgefunc;
使用如下:
bridgefunc.scanCode().then(res => {
if (res.result == 1) {
console.log('扫码结果:', res.data);
}
})
原创不易,欢迎留言指正。