一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第2天,点击查看活动详情。
很多人人觉得混合开发很神秘,听到jsBridge感觉很高大上,其实这只是开发中一个小技巧罢了。今日诸君,且听我细细道来。
原生在本文主要指iOS开发 或 Android开发
什么是混合开发
混合开发简单而言就是原生(iOS or Android)做一个壳,里面是一个webview,用来展示网页。这种开发方式的优点是能够极大提高上架成功率,并且能够实现热更新。缺点就是体验差,开发效率不高,我具体是说一下。
-
提高上架成功率 众所周知。大部分App市场上架时都会审核代码,所以基本所有开发都会比如规避关键词,混淆一些代码等等,但是随着审核的愈发严格,上架率依然是一个很大的问题。混合开发中,原生其实只提供一些接口,大部分显示,功能都是网页提供的,而展示网页只需要一个网址,可以通过转译等方式隐藏地址,规避审核。
-
热更新 这个不用说,更新一个网页比更新App要容易得多
-
体验差 我认为的体验差主要由于2方面原因:
1、加载白屏。网页而言,无论怎么优化,打开时肯定会白屏,只是白屏时间长短的问题。而且webview初始化时也会白屏,这个问题也不好解决。参照一个大厂的App,基本都是加一个进度条搞定,十分粗暴。
2、性能不够。对于大量计算、高性能动画,大文件处理上,网页确实不行。
- 开发效率不高 以前需要一个人开发的,现在变成2个,增加了许多沟通成本。而且网页嵌入App后,调试也变得困难,例如如果你调试iOS的App,你可能得有mac和一根数据线。
什么是jsBridge
当一个原生做皮,内里却是一个网页,必不可免的需要通信,这个通信就叫做jsBridge。
我用打电话做个例子,当你打电话时,你需要先选择一个电话号码,拨打,接通后第一句一般也是你好之类。
当我们调用一个原生的方法也很类似,你需要先和原生沟通方法名,再调用这个方法名,传参过去。
这里有一个简单的例子
const msg = {
func: 'send',
params: {},
cb:'callback'
}
window.webkit.messageHandlers.myHandler.postMessage(msg)
iOS与安卓的不同点
在设计jsBridge时,我发现Android与iOS有一些不同点。
-
参数格式 iOS中可以传一个对象过去,他们那边可以自动接收并解析,安卓不行,安卓建议使用JSON字符串。
-
返回值 安卓可以同步地返回一些值,iOS不行.
处理jsBridge中的异步
在iOS中,所有结果都是异步的通过一个回调接受返回值的,这也就导致,那怕这个操作在原生中是同步的方法,在js里也全是异步。
例如,我们获取App的包名
const msg = {
func: 'getBundleId',
params: {},
cb:'callback'
}
window.webkit.messageHandlers.myHandler.postMessage(msg)
此时,iOS则会调用window.callback这个方法,把包名传给你,我们可以假设原生的伪代码
evaluateJavaScript(`window.callback(someData)`)
这样会导致我们无法在之前的函数里获取结果,造成了事实上的异步。
一开始,我是这样处理的,通过一个全局变量去查看jsBridge是否返回结果,这样虽然也能完成,但十分简单粗暴,如果同时调用太多,容易卡顿,毕竟定时器就是这样。
window.bridgeStatus = {}
const getBundleId = (cb)=>{
const msg = {
func: 'getBundleId',
params: {},
cb:'callback'
}
try{
window.webkit.messageHandlers.myHandler.postMessage(msg)
}catch(e){
console.log(e.message)
}
return new Promise((resolve, reject) => {
if(xxx){resolve()}
}, 100)
}
直到后来,我了解到了一个JS原生的方法customEvent
,也就是原生js中的观察者模式。
我们完全可以通过这个方法实现比我之前更优雅的写法。
export const stopBridgeFunc = (name, key)=>{
const evt= new CustomEvent('bridgeResponse',{detail:{
name,
key
}})
window.dispatchEvent(evt)
}
const send = (id,cb)=>{
const msg = {
func: 'send',
params: {id},
cb
}
try{
window.webkit.messageHandlers.myHandler.postMessage(msg)
}catch(e){
console.log(e.message)
}
return new Promise((resolve, reject) => {
const timer = setTimeout(() => {
clearTimeout(timer)
resolve('timeout')
},5 * 1000)
window.addEventListener('bridgeResponse',({ detail:{name,key} }) => {
//通过方法名和一个标志校验是否是当前执行的,适用于同时多个jsBridge
if (name === msg.func && key === id) {
clearTimeout(timer)
resolve(true)
}
},
{once:true}
)
}
此时,我们只需要在callback中调用stopBridgeFunc即可
window.callback = (obj)=>{
stopBridgeFunc(obj.msg.func,obj.msg.params.id)
}
send('123','callback')
setTimeout(()=>{
send('234','callback')
},10)