当vue遇上Hybrid APP

Hybrid APP 优势

网上查了一圈,没有找到符合我内心的描述,所以就算忽略吧,反正优点很多,这不是我们要说的重点,忽略几百字或者几十字总有的

解决方案--jsbridge

Hybrid网上很多,比较大型的方案还是推荐jsbridge,其最大优势在于方便于扩展,下面文章都是基于jsbridge来说的。
安利两个成熟的库
iOS WebViewJavascriptBridge
Android JsBridge
基本原理
这也不是这篇帖子的重点,上张网上盗来的图,个人看来这个图有些地方有点过期了,回头写jsbridge的原理的时候,再详细来说。
当vue遇上Hybrid APP

JS核心函数认识

1、获取Native植入对象,后续的调用都是使用的这个对象

/**
 * 获取客户端的桥接对象
 * @returns {Promise<any>}
 */
setupWebViewJavascriptBridge: function () {
  return new Promise(resolve => {
    if (window.WebViewJavascriptBridge) {
      // 项目正常运行时window上已经挂载了客户端的桥接对象WebViewJavascriptBridge,直接使用对象
      return resolve(WebViewJavascriptBridge);
    }
    if (window.WVJBCallbacks) {
      // 已创建了回调队列,直接往回调队列添加回调函数
      return window.WVJBCallbacks.push(resolve);
    }
    // 页面初始化时在客户端注入桥接对象前定义个回调函数队列,客户端在初始化后会依次调用队列中的函数
    window.WVJBCallbacks = [resolve];
    // 用隐藏的iframe,来触发url scheme,从而触发WebViewJavascriptBridge的注入
    var WVJBIframe = document.createElement("iframe");
    WVJBIframe.style.display = "none";
    WVJBIframe.src = "wvjbscheme://__BRIDGE_LOADED__";
    document.documentElement.appendChild(WVJBIframe);
    setTimeout(function () {
      document.documentElement.removeChild(WVJBIframe);
    }, 0);
  })
}

2、js调用客户端

/**
 * h5调用客户端协议
 * @param bridgeOpStr 方法名称
 * @param paras 调用协议的参数
 * @returns {PromiseLike<any | never> | Promise<any | never>}
 */
callHandler: function (bridgeOpStr, paras) {

  return this.setupWebViewJavascriptBridge().then(bridge => {
    return new Promise(resolve => {
      bridge.callHandler(bridgeOpStr, paras, (response) => {
        resolve(response);
      });
    })
  });
}

3、客户端调用js

/**
 * 客户端调js
 * @param bridgeOpStr   方法名
 * @returns {PromiseLike<any | never> | Promise<any | never>}
 */
registerHandler: function (bridgeOpStr) {
  return this.setupWebViewJavascriptBridge().then(bridge => {
    return new Promise(resolve => {
      bridge.registerHandler(bridgeOpStr, (data, responseCallback) => {
        // data 传过来的数据, responseCallback 回调函数
        resolve(data, responseCallback);
      });
    })
  });
}

封装

散装的js函数足够可以使我们完成项目了,不过为了更好的使用,我们应该将其封装到一个对象中,然后全局使用这个对象,这样做有几个优点。
1、方便调用。
2、因为jsbridge天生异步,所以我们有时候需要借助队列来控制一些逻辑。
3、版本等函数管理
主要封装内容:

  • 版本管理,方便APP后续升级,在市场上形成多版本状态。
  • 开发版本回调管理,方便开发过程中的调试
  • 统一用Promise调用,并且返回结构统一。

算了吹多了没意思,直接上代码

const dev = process.env.NODE_ENV !== 'production'

/**
 * 业务函数最终返回的是 promise, 其回调结果都是对象,
 * result 状态: 1 成功, 0 失败;
 * msg    提示信息。
 * data   业务数据。
 */

let bridgefunc = {
  // 当前协议版本
  version: -1,
  /**
   * 获取当前协议版本,都支持
   * @returns {*}
   */
  getAppProtocolVersion() {
    let jsonData = {};
    jsonData.type = "getAppProtocolVersion";
    return this.callHandler("phonebridge", jsonData);
  },
  // 初始化版本号
  initVersion() {
    this.getAppProtocolVersion().then(res => {
      if (res.result == 1) {
        this.version = res.data;
        this.consumeNoViersionQueue()
      }
    })
  },
  // 没有版本的时候,交互先放入队列等版本回来再消费
  noVersionQueue: [],
  // 消费没有version队列
  consumeNoViersionQueue() {
    while (this.noVersionQueue.length) {
      let callback = this.noVersionQueue.shift()
      callback.call(this, true);
    }
  },
  // 函数版本和测试支持,如果不包含函数对象,表示都支持。
  funcSupportVersion: {
    scanCode: {
      version: 1.0,
      devValue: '1234567890' // 开发环境回调值
    },
    pageWillShow: {
      version: 2.5 // 回调场景默认值没意义,立即是不正确的,不写开发环境回到值,就不会触发
    }
  },
  // 检查协议是否支持
  checkSupport (bridgeOpStr, resolve, callback) {

    if (dev) {
      if (this.funcSupportVersion[bridgeOpStr] && this.funcSupportVersion[bridgeOpStr].devValue != undefined) {
        resolve({
          result: 1,
          msg: '这是开发环境回调',
          data: this.funcSupportVersion[bridgeOpStr].devValue
        })
      } else {
        resolve({
          result: 0,
          msg: '开发环境,不支持调用'
        })
      }
      callback(false);
    } else if (bridgeOpStr == 'getAppProtocolVersion') {
      // 获取version直接继续,否则死循环
      callback(true);
    } else if (this.version == -1) {
      this.initVersion()
      // 还没有获取到版本号,加入等待队列
      this.noVersionQueue.push(callback)
    } else if (!this.funcSupportVersion[bridgeOpStr] || !this.funcSupportVersion[bridgeOpStr].version || this.version >= this.funcSupportVersion[bridgeOpStr].version) {
      // 没有设置支持函数支持版本
      callback(true);
    } else {
      resolve({
        result: 0,
        msg: '当前APP版本过老,不支持该函数,函数名:' + bridgeOpStr
      })
      callback(false);
    }
  },
  /**
   * h5调用客户端协议
   * @param bridgeOpStr 方法名称
   * @param paras 调用协议的参数
   * @returns {PromiseLike<any | never> | Promise<any | never>}
   */
  callHandler (bridgeOpStr, paras) {

    return this.setupWebViewJavascriptBridge().then(bridge => {
      return new Promise(resolve => {
        this.checkSupport(bridgeOpStr, resolve, (result) => {
          if (result) {
            bridge.callHandler(bridgeOpStr, paras, (response) => {
              resolve({
                result: 1,
                data: response
              });
            });
          }
        })
      })
    });
  },
  /**
   * 客户端调js
   * @param bridgeOpStr   方法名
   * @returns {PromiseLike<any | never> | Promise<any | never>}
   */
  registerHandler (bridgeOpStr) {
    return this.setupWebViewJavascriptBridge().then(bridge => {
      return new Promise(resolve => {
        this.checkSupport(bridgeOpStr, resolve, (result) => {
          if (result) {
            bridge.registerHandler(bridgeOpStr, (data, responseCallback) => {
              // data 传过来的数据, responseCallback 回调函数
              resolve({
                result: 1,
                data: data,
                responseCallback: responseCallback
              });
            });
          }
        })
      })
    });
  },
  /**
   * 获取客户端的桥接对象
   * @returns {Promise<any>}
   */
  setupWebViewJavascriptBridge () {
    return new Promise(resolve => {
      if (window.WebViewJavascriptBridge) {
        // 项目正常运行时window上已经挂载了客户端的桥接对象WebViewJavascriptBridge,直接使用对象
        return resolve(WebViewJavascriptBridge);
      }
      if (window.WVJBCallbacks) {
        // 已创建了回调队列,直接往回调队列添加回调函数
        return window.WVJBCallbacks.push(resolve);
      }
      // 页面初始化时在客户端注入桥接对象前定义个回调函数队列,客户端在初始化后会依次调用队列中的函数
      window.WVJBCallbacks = [resolve];
      // 用隐藏的iframe,来触发url scheme,从而触发WebViewJavascriptBridge的注入
      var WVJBIframe = document.createElement("iframe");
      WVJBIframe.style.display = "none";
      WVJBIframe.src = "wvjbscheme://__BRIDGE_LOADED__";
      document.documentElement.appendChild(WVJBIframe);
      setTimeout(function () {
        document.documentElement.removeChild(WVJBIframe);
      }, 0);
    })
  },
  /**
   * // 扫一扫,版本1.0及之后可以调用
   * @returns {*} promise 返回 code
   */
  scanCode() {
    let jsonData = {};
    return this.callHandler("scanCode", jsonData);
  },
  /**
   * 注册回调函数,页面从其他地方回来了,版本2.5及之后可以调用
   * @returns {*}
   */
  pageWillShow() {
    let jsonData = {};
    return this.registerHandler("pageWillShow", jsonData);
  }
};

export default bridgefunc;

使用如下:

bridgefunc.scanCode().then(res => {
  if (res.result == 1) {
    console.log('扫码结果:', res.data);
  }
})

原创不易,欢迎留言指正。

上一篇:es6-Promise解决回调地狱


下一篇:JS之Promise问题