JSBridge

背景

原生APP虽然稳定,但是迭代比较慢,发版的时候需要商店审核,同个功能在不同端需要写两套代码(这里主要是ios系统和安卓系统),但是随着H5的发展,原生APP内核中支持用webview打开H5页面,所以了混合APP的诞生了。这时候H5和原生APP的通信成了需要解决的问题,JSBridge应运而生。

定义

JSBridge是一种JS实现的H5和客户端交流的桥梁的方法统称,解决两者的通信问题,可以让H5调用原生APPAPI,如查看相册、扫码等能力,原生APP可以调用H5中定义的方法。它更多的是一种称呼,实现方法并不是固定的。

APP与H5通信

p.s. 以下客户端代码仅供理解大概原理,写法上并不一定完全正确。

安卓

安卓调用H5中的方法

这里要区分版本号,在4.4版本之前:

// 获得当前的WebView对象
mWebView = new WebView(this);
// 调用H5中的方法
mWebView.loadUrl("javascript:方法名('参数1, 参数2...')");

如果是4.4版本之后:

mWebView = new WebView(this);
mWebView.evaluateJavascript("javascript:方法名('参数1, 参数2...')", new ValueCallback(){
  @Override
  // 当H5的方法触发完毕之后,就会调用这个方法,value即H5方法的返回值
  public void onReceiveValue(String value) {
  
  }
})

从上面不能看出4.4版本之后的调用多了个回调函数结果的返回~

H5调用安卓中的方法

首先需要先在安卓中定义一些方法供H5使用:

private Object getJSBridge(){
  Object insertObj = new Object(){
    // @JavascriptInterface
    public String foo(){
      return 'foo';
    }
    
    // @JavascriptInterface
    public String foo2(final String param){
      return 'foo2:' + param;
    }
  }
   
  // 定义了一个实例化对象,内含两个方法
  return insertObj;
}
// 获取WebView的设置对象
WebSettings webSettings = mWebView.getSettings();
// 设置Android运行JS脚本
webSettings.setJavaScriptEnabled(true);
// 暴露一个叫做JS的对象到WebView的全局环境
mWebView.addJavascriptInterface(getJSBridge(), 'JSBridge');

接下来在H5中调用:

window.JSBridge.foo();
window.JSBridge.foo2('test');

IOS

IOS调用H5中的方法

先在H5中定义函数:

function changeBgColor(){
  document.body.style.backgroundColor = 'pink';
}

然后在IOS中调用:

class ViewController: UIViewController, WKNavigationDelegate {

  override func viewDidLoad(){
    super.viewDidLoad();
    
    // 让当前页面加载一个webview
    let webView = WKWebView(frame: self.view.bounds);
    webView.navigationDelegate = self;
    self.view.addSubview(webView);
    
    // 让webview加载我们指定的html
    let url = URL(string: "http://xxxx.com/index.html");
    let request = URLRequest(url: url!);
    webView.load(request);
  }
  
  
  // 当webview加载完成后触发
  func webView(_webView: WKWebView, didFinish navigation: WKNavigation!){
    // 调用H5中的代码
    webView.evaluateJavascript("changeBgColor()");
  }
}
H5调用IOS中的方法
class ViewController: UIViewController, WKNavigationDelegate, WKScriptMessageHandler {

  override func viewDidLoad(){
    // ...
    
    // 为webview添加方法名检测
    wbeView.configuration.userContentController.add(self as WKScriptMessageHandler , name: "useNative");
  }
  
  
  // ...
  
  // 监听H5
  func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
    print("传来的数据源:", message.body);
  }
}

H5中调用:

document.querySelector('input').onclick = function(){
  // window.webkit.messageHandlers.【在IOS中定义的方法名】.postMessage
  window.webkit.messageHandlers.useNative.postMessage('hello world')'
}

URL Scheme

上述APP调用H5方法的行为可以叫做API注入,其实还有另一一种方法,借助的是URL scheme

URL Scheme是一种类似于url的链接,是为了方便app直接互相调用设计的,形式和普通的 url 近似,但是可以自定义。比如微信的 scheme 'weixin://' 开头。

我们可以在H5中跳转定义好的URL Scheme,然后在原生APP中拦截他们做出相应的反应。以IOSH5为例子:

document.querySelector('input').onclick = function(){
  // 创建iframe
  const iframe = document.createElement('iframe');
  // 设置URL scheme
  iframe.src = "armouy://click";
  // 设置尺寸
  iframe.style.width = 0;
  iframe.style.height = 0;
  // 添加到页面上
  document.body.appendChild(iframe);
  // 移除
  iframe.parentNode.removeChild(iframe);
}

IOS中进行拦截:

class ViewController: UIViewController, WKNavigationDelegate, WKScriptMessageHandler {

  override func viewDidLoad(){
    // ...
  }
  
  
  // ...
  
  // 监听H5
  func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, descisionHandler:  @escaping (WKNavigationActionPolicy) -> Void) {
    // 获取URL
    let url = navigationAction.request.url?.absoluteString;
    if(url === "armouy://click") {
      // 做一些拦截处理
      // do someting
      descisionHandler(.cancel);
    }else {
      descisionHandler(.allow);
    }
  }
}

这种方式是早期使用的,优点是支持IOS6,缺点也很明显

  • frame的链接长度有限制(有些方案为了规避 url 长度隐患的缺陷,在 iOS 上采用了使用 Ajax 发送同域请求的方式,并将参数放到 head body 里。这样,虽然规避了 url 长度的隐患,但是 WKWebView 并不支持这样的方式。);
  • 创建请求会有耗时

不使用locaiton.href代替iframe.src的原因是:如果通过 location.href 连续调用 Native,很容易丢失一些调用。

JSBridge简易封装

从上面的API注入方式来看,还是不太灵活,所以在实际开发中,我们会封装一些代码:

// 是否是APP
const isApp(){
  const agent = window.navigator.userAgent.toLowerCase();
  // 这里要根据实际项目写正则去判断
  return true;
}

// 是否是IOS
const isIos(){
  return /ios|iphone|ipad|ipod/.test(window.navigator.userAgent.toLowerCase())
}

// 是否是安卓
const isAndroid(){
  return /android/.test(window.navigator.userAgent.toLowerCase())
}

// 判断客户端是否注册了该方法
function canInvoke(method){
  try {
      // 这里的window.JSBridge不是固定的,取决于公司项目,我们前面定义的就是JSBridge
      if (this.isAndroid) return !!window.JSBridge[method]
      if (this.isIos) return !!window.webkit.messageHandlers[method]
      return false
    } catch (error) {
      console.warn('canInvoke-客户端未初始化jsBridge', error)
      return false
    }
}

// H5给APP发送信息
function postMessageToApp(options){
  try {
      const { method, data = {} } = options
      if (this.isIos) {
          window.webkit.messageHandlers[method].postMessage(data)
        } else if (this.isAndroid) {
          // 这里的window.JSBridge不是固定的,取决于公司项目,我们前面定义的就是JSBridge
          window.JSBridge(JSON.stringify(data))
        }
    } catch (error) {
      console.warn('postMessageToApp', error, options.method)
    }
}

// H5调用APP方法
function invokeNative(options) {
  if (!this.isApp) return console.warn('invokeNative', '非app环境')
  // 如果客户端已经注册了这些方法,那么就直接调用
  if (this.canInvoke(options.method)) {
    postMessageToApp(options);
  }else {
    // ...
  }
}
// 这里的场景是H5调用APP的方法,并希望APP调用方法之后,通知H5,执行H5的回调方法
(function(){
  let id = 0;
  const callbacks = {};
  
  window.JSBridge = {
    // 调用原生方法
    invoke: function(method, callback, data = {}) {
      // 唯一ID
      const thisId = id++;
      callbacks[thisId] = callback;
      // 通过原生提供的对象调用原生功能
      invokeNative({
         method,
         data,
         callbackId: thisId
      })
    },
    
    // 接收原生消息
    receiveMessage: function(msg){
      const method= msg.method;
      const data = msg.data || {};
      const callbackId = msg.callbackId;
      
      if(callbackId && callbacks[callbackId]) {
        callbacks[callbackId](data);
      }else {
        // ...
      }
    }
  }
  
})()

总结

理解JSBridge的作用,并粗略地介绍了APPH5之间通信机制,最后封装了一个简易的JSBridge

参考链接


如果错误欢迎指出,感谢阅读~

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值