混合开发JSBridge

635 阅读3分钟

Native 和 H5 都有着各自的优缺点,为了满足业务的需要,实际项目的开发过程中往往会融合两者进行 Hybrid 开发。

WebView

WebView为移动端提供的运行JavaScript的环境,是系统渲染Web网页的一个控件,可与页面JavaScript交互,实现混合开发。可以把Webview看作是一个性能打八折的移动浏览器。

WebView控件除了能加载指定的url外,还可以对URL请求、JavaScript的对话框、加载进度、页面交互进行强大的处理,之后会提到拦截请求、执行JS脚本都依赖于此。

JSBridge

Hybrid模式下,H5会经常需要使用Native的功能,比如打开二维码扫描、调用原生页面、获取用户信息等,同时Native也需要向Web端发送推送、更新状态等,而JavaScript是运行在单独的JS Context中(Webview容器、JSCore等),与原生有运行环境的隔离,所以需要有一种机制实现Native端和Web端的双向通信,这就是JSBridge。

Native端调用Web端

JavaScript作为解释性语言,最大的一个特性就是可以随时随地地通过解释器执行一段JS代码,所以可以将拼接的JavaScript代码字符串,传入JS解析器执行就可以,JS解析器在这里就是WebView。(因为webView就存在App里面,所以native端可以拿到webView这个对象,然后用这个webView去解释执行js代码)。

Android 4.4之后提供了evaluateJavascript来执行JS代码,并且可以获取返回值执行回调:

webView.evaluateJavascript(javaScriptString, new ValueCallback<String>() {
   @Override
   public void onReceiveValue(String value){
      xxx
   }
});

注意:原生端调用web的方法,方法必须挂载到web端的window对象下面

Web端调用Native端

Web调用Native端主要有两种方式:

拦截Webview请求的URL Schema

URL Schema是类URL的一种请求格式,格式如下: <protocol>://<host>/<path>?<qeury>#fragment

我们可以自定义JSBridge通信的URL Schema,比如:jsbridge://showToast?text=hello,下图是微信提供的Schema:

image.png

Native加载WebView之后,Web发送的所有请求都会经过WebView组件,然后Native可以重写WebView里的方法,对拦截Web发起的请求,我们对请求的格式进行判断:

  • 如果符合我们自定义的URL Schema,对URL进行解析,拿到相关操作、操作,进而调用原生Native的方法
  • 如果不符合我们自定义的URL Schema,我们直接转发,请求真正的服务

Web发送URL请求的方法有这么几种:

  1. a标签
  2. location.href
  3. 使用iframe.src
  4. 发送ajax请求 这些方法,a标签需要用户操作,location.href可能会引起页面的跳转丢失调用,发送ajax请求Android没有相应的拦截方法,所以使用iframe.src是经常会使用的方案。安卓和IOS都提供了相应的拦截方法,比如安卓使用shouldOverrideUrlLoading方法进行拦截。下面我们来封装一个js-sdk:
const jsSdk = {
  invoke(url, data, onSuccess, onError) {
    const iframe = document.createElement('iframe')
    iframe.style.visibility = 'hidden'
    document.body.appendChild(iframe)
    iframe.onload = () => {
      const content = iframe.contentWindow.document.body.innerHTML
      onSuccess(JSON.parse(content))
      document.body.removeChild(iframe)
    }
    iframe.onerror = () => {
      onError()
      document.body.removeChild(iframe)
    }
    iframe.src = `jsbridge://${url}?data=${data}`
  },
  fn1(data, onSuccess, onError) {
    this.invoke('/fn1', data, onSuccess, onError)
  },
  fn2(data, onSuccess, onError) {
    this.invoke('/fn2', data, onSuccess, onError)
  }
}

这种方式适用于异步情况下的调用,通过回调函数拿到结果

向Webview中注入JS API

这个方法会通过webView提供的接口,App将Native的相关接口注入到JS的Context(window)的对象中,一般来说这个对象内的方法名与Native相关方法名是相同的,Web端就可以直接在全局window下使用这个暴露的全局JS对象,进而调用原生端的方法。

Android提供了addJavascriptInterface注入:

// 注入全局JS对象 
webView.addJavascriptInterface(new NativeBridge(this), "NativeBridge");

class NativeBridge { 
// 增加JS调用接口 
    public void showNativeDialog(String text) { 
        new AlertDialog.Builder(ctx).setMessage(text).create().show(); 
    } 
}

在Web端直接调用这个方法即可:

window.NativeBridge.showNativeDialog('hello');

这种方式适用于同步方式的调用