Vue H5页面与Android和IOS交互(Vue部分)

1,593 阅读5分钟

美好的结局总有一个良好的开端

最近在调研一个需求,需要通过移动数据采集基站与安卓平板通信,言外之意就是我有一个基站,当我的平板插上USB数据线后,我期望通过USB将基站数据发送到平板,在平板上进行一系列的操作。说实话,一开始真是毫无头绪,和后端讨论了很久,最初看到了H5+的SQLite库,决定用uniapp来做一个前端控制的数据库,一切操作都由前端控制,奈何咱们这个系统面向对象是学校,几千个学生的运动检测,一包数据由若干条组成。考虑到所有的操作都在前端进行着实难度太大而且影响性能,果断放弃了SQLite,那怎么办呢????

初识jsBridge

某天早上,后端发给我了一个git仓库说这个好像可以实现我们的混合开发,对于没有接触过这方面需求的我刚看到也挺懵,不过既然可行,那就试试吧...

js调用原生方法/原生调用js方法

这里首先说明一下,我们这篇文章不会出现安卓代码,我们以解决问题为目的,不会有太多与文章无关的内容出现,也是为了让大家一目了然这么一个操作顺序,所以想了解安卓编码的同学可以直接看看上面提到的仓库。

其实仓库里写的很清楚咱们js应该怎么样去写,所以我就直接上代码

function setupWebViewJavascriptBridge(callback) {
  if (window.WebViewJavascriptBridge) {
    return callback(window.WebViewJavascriptBridge)
  }
  if (window.WVJBCallbacks) {
    return window.WVJBCallbacks.push(callback)
  }
  window.WVJBCallbacks = [callback]
  let WVJBIframe = document.createElement('iframe')
  WVJBIframe.style.display = 'none'
  WVJBIframe.src = 'https://__bridge_loaded__'
  document.documentElement.appendChild(WVJBIframe)
  setTimeout(() => {
    document.documentElement.removeChild(WVJBIframe)
  }, 0)
}
export default {
  callhandler(name, data, callback) {
    setupWebViewJavascriptBridge(function (bridge) {
      bridge.callHandler(name, data, callback)
    })
  },
  registerhandler(name, callback) {
    setupWebViewJavascriptBridge(function (bridge) {
      bridge.registerHandler(name, function (data, responseCallback) {
        callback(data, responseCallback)
      })
    })
  }
}

这里面把WebViewJavaScriptBridge封装了一下,就可以直接在外部调用callhandlerregisterhandler方法了

由于我这里是使用的vue3并且只是在一个组件里使用到了,所以就没有进行全局注册了

import bridge from '@/config/bridge'

onMounted(() => 
  bridge.registerhandler('getNowTime', (data, responseCallback) => {
    dataMap.value = JSON.parse(data)
    var responseData = '我收到了数据'
    responseCallback(responseData)
  })
})

const getData = () => {
  console.log('接收数据')
  bridge.callhandler('submitFromWeb', { id: '123' }, (response) => {
    resData.value = response
    console.log("Javascript was loaded by Android and successfully loaded.");
  })
}

好的,其实到这里都很简单,上面提及到的仓库都有教大家如何去编写js调用原生和原生调用js,但问题也就出在这里。至今我都不知道他的demo是如何跑通的,我们按照他的实例代码,写入我们自己的项目会发现接收不到回调,这是为什么呢?在js中注册了方法,在ios和android两个平台使用,结果是ios调用ok,android死活调用不了,最重要的是js去调用android的方法却可以。那我们就去看看安卓到底怎么回事,查阅了一些资料后得知在安卓上面需要init,所以我们改造一下

  if (/(iPhone|iPad|iPod|iOS)/i.test(navigator.userAgent)) {
    setupWebViewJavascriptBridge(function(bridge){
      $bridge = bridge
    })
  }else if(/(Android)/i.test(navigator.userAgent)) {
   connectWebViewJavascriptBridge(function(bridge){
     bridge.init((message, responseCallback) => {
       responseCallback('get message success')
     })
     $bridge = bridge
   })
  }

加上这一个判断,如果是Android我们就使用brige.init()来执行方法,根据vue-bridge-webview这个插件最终将我们的代码改写为如下形式

var bridgeConfig = {
  bridgeWebViewDelay : 0,
  callHandle : {}, // bridge android / ios
  silent : false
}

var $bridge = {
  registerHandler : function (name,callback) {
    if(bridgeConfig.silent){
      console.log(name,' register handler failure')
    }
  },
  callHandler : function (name, params, callback) {
    if(bridgeConfig.silent){
      console.log(name,' call handler webView failure')
    }
  }
};

function setupWebViewJavascriptBridge (callback) {
  if (window.WebViewJavascriptBridge) {
    return callback(window.WebViewJavascriptBridge)
  }
  if (window.WVJBCallbacks) {
    return window.WVJBCallbacks.push(callback)
  }
  window.WVJBCallbacks = [callback]
  let WVJBIframe = document.createElement('iframe')
  WVJBIframe.style.display = 'none'
  WVJBIframe.src = 'https://__bridge_loaded__'
  document.documentElement.appendChild(WVJBIframe)
  setTimeout(() => {
    document.documentElement.removeChild(WVJBIframe)
  }, 0)
}

  /* 用于创建桥接对象的函数 , android 初始化 */
  function connectWebViewJavascriptBridge(callback) {
    //如果桥接对象已存在,则直接调用callback函数
    if (window.WebViewJavascriptBridge) {
      callback(WebViewJavascriptBridge)
    }
    //否则添加一个监听器来执行callback函数
    else {
      document.addEventListener('WebViewJavascriptBridgeReady', function () {
        callback(WebViewJavascriptBridge)
      }, false)
    }
  }

  if (/(iPhone|iPad|iPod|iOS)/i.test(navigator.userAgent)) {
    setupWebViewJavascriptBridge(function(bridge){
      $bridge = bridge
    })
  }else if(/(Android)/i.test(navigator.userAgent)) {
   connectWebViewJavascriptBridge(function(bridge){
     bridge.init((message, responseCallback) => {
       responseCallback('get message success')
     })
     $bridge = bridge
   })
  }

export default {
  callhandler (name, data, callback) {
    setupWebViewJavascriptBridge(function (bridge) {
      $bridge.callHandler(name, data, callback)
    })
  },
  registerhandler (name, callback) {
    setupWebViewJavascriptBridge(function (bridge) {
      $bridge.registerHandler(name, function (data, responseCallback) {
        callback(data, responseCallback)
      })
    })
  }
}

如此一来,我们就可以成功的执行安卓的回调啦~不过这里至今我还是不太明白为什么。好啦,方法也能执行了,数据也能传递了,但后端这个时候又抛出了一个问题来了,他说这个插件,安卓在执行方法的时候一直占用的是主线程,对于我们每一秒钟都要调用方法来说会很卡,实际效果也是如此,所以他重新又找了个插件,SDBridgeJava,半个月前才推送到git上的新插件,这个插件就能实现异步请求并且放在子线程中执行,这样一来就能大大提高我们的性能,说干就干,其实和上面的写法也大同小异,只是在接收数据的时候对data的解析不同罢了,对于WebViewJavaScriptBridge的封装就可以直接还原到最顶部的写法,接受数据和传递数据则变为了{key:value}的形式,非常简单,这里就直接贴代码:

onMounted(() => {
  bridge.registerhandler('getNowTime', (data, responseCallback) => {
    dataMap.value = JSON.parse(data.AndroidKey)
    var responseData = '我收到了数据'
    responseCallback({'msg': responseData})
  })
})
const getData = () => {
  console.log('接收数据')
  bridge.callhandler('submitFromWeb', { id: '123' }, (response) => {
    resData.value = response.result
    console.log("Javascript was loaded by Android and successfully loaded.");
  })
}

由于我们的项目确定是在安卓平板进行开发,故没有对设备进行判断,如果需要在IOS上开发的话可以按照SDBridegJava这个插件的文档在接收数据的时候,response.result里进行判断:


if (result === "iOS") {/* ... */}
else if (result === "Android") { /* ... */}

正如文档所说:JS tries to call the native method to judge whether it has been loaded successfully and let itself know whether its user is in android app or IOS app

到底为止,我们就可以成功的和原生进行数据传递以及通信了。不过当我们打包后又会出现问题: 安卓访问我们的页面是通过webview.loadUrl()访问我们的页面,当我们打包后安卓放在自己的项目里路径就不再是http开头而是file,但file协议并不支持,此时是会跨域的,不过安卓是可以配置跨域的,这个也不用我们前端去操心。但是我们会发现,尽管配置好了跨域以后,但是我们的图片资源,我们的js资源却访问不到,这真是一步一个坑啊,一番查阅资料后,将vite.config.tsbase: '/'改为'./'再打包后,发现就可以访问到我们的资源了。真是一波三折啊~~~~

至此,我们Vue H5页面与原生Android的交互也就实现了,并且由于使用了SDBridgeJava这个插件,传输数据也不会有卡顿的现象。对于我来说这也算是一个新知识,所以记录一下。