当vue遇上Hybrid APP

Hybrid APP 优势

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

解决方案--jsbridge

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

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);
  }
})

原创不易,转载请注明出处,欢迎留言指正。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 201,312评论 5 473
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,578评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,337评论 0 333
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,134评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,161评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,303评论 1 280
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,761评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,421评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,609评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,450评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,504评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,194评论 3 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,760评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,836评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,066评论 1 257
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,612评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,178评论 2 341

推荐阅读更多精彩内容