混合开发入门之jsBridge

194 阅读4分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 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)