likes
comments
collection
share

写一个订阅模式,实现UniApp与H5之间的互相通信

作者站长头像
站长
· 阅读数 10

首先,项目是混合app即uniapp做框架,内容八成是H5,所以通信就成了重中之重了

因为我也是第一次接触app端开发,而且uniapp开发不如vue熟悉,其中就带有很多疑惑点了

  1. 用uni开发打包成app的话,那还能用uni的方法嘛,比如uni.scan之类的 (因为之前我直接用uni开发的小程序打包成app,打开后页面一篇空白)
  2. 打包成app要那些步骤,怎么搞。

后来重新搭建uni项目后,发现我真的多虑了,很多时候时间一下答案就出来了

  1. 对于第一点回答,你尽管用uni的方法即可,人家内部会处理好这些的啦~
  2. 打包app的话,安卓和ios都不一样的,下面介绍两种方式
  • 第一种 去dcloud官网 -> 我的应用 -> 点击应用名称 -> Android证书 -> 然后点生成 如图

写一个订阅模式,实现UniApp与H5之间的互相通信

  • 第二种 让后端给你搞一个自定义证书,比较麻烦,不过不用我们动手比较舒服(好像要用as去生成)
  • 至于IOS 我这边刚开发,账号都没申请下来,等到那一天我再补充这个坑

其他坑末尾补充

到了通信这块了,虽然通信都是用uni.webview.js (下面统称wv.js) 来实现的,但是网上参次不齐的资料,写法多数有问题,不排除我的问题(我全抄了都出错),下面说说我的写法

因为是混合app,H5页面不止我一方,还有其他部门第三方等接入的,类似小程序接入H5页面一样,H5需要使用到wx.jssdk提供的方法例如扫码跳转等方法,因为不是在微信环境,所以sdk需要我们来写一份实现

实现过程

再该业务情景下,我们需要提供一个方法给第三方,让他调我们的方法,然后把方法结果传回去给他

  1. 使用 wv.js提供的postMessage作为桥梁,将通信发送给对方

uniapp页面处理如下

html:
<template>
	<web-view @message="message" :src="''"></web-view>
</template>

js: 

    // 获取H5页面消息
    async message(e) {
            let that = this;
            // 通过wv.js传回来的数据,是嵌套这么多层的...
            let eventParams = e?.detail?.data.length ? e?.detail?.data[0] : {};
            // 这个做法是匹配后续的一个js服务,拿到对应的服务执行,然后把结果返回给H5
            // 下面这两个参数是自定义的,1是参数 2是执行的方法名
            let { params = null, type } = eventParams;
            // 从sdk.js中解构出对应的方法,没有匹配上就用sdk.js默认的defaultFn匹配下
            let { [type]: func = sdk['defaultFn']  = sdk;
            // 默认服务为成功,type为方法名,返回给H5时候,H5可以知道是那个方法执行的
            let serviceMsg = {
                    status: 'success',
                    type: type
            };
            // 执行方法,如果方法报错则在catch中设置执行为失败状态
            let res = await func(params).catch((err) => {
                    serviceMsg.status = 'fail'
                    serviceMsg = {
                            ...serviceMsg,
                            error: err
                    }
            })
            serviceMsg = {
                    ...serviceMsg,
                    ...res
            }
            // 开始执行,通知H5执行完成,把结果返回去
            that.postH5Mes(serviceMsg);
    },
    postH5Mes(msg) {
        // 如果当前没有webview实例,则重新赋值一次,该操作是避免H5一开始调用wv还未生成报ecalJS 未定义, 在页面初始化的时候也要赋值一下给wv,就是下面if里面的语句,我没写而已~
        // #ifdef APP-PLUS
        if (!this.wv) {
                let currentWebview = this.$scope.$getAppWebview();
                this.wv = currentWebview.children()[0];
        }
        // 自定义方法getUNIMsg
        this.wv.evalJS("document.dispatchEvent(new CustomEvent('noticeH5',{detail: {msg:" + JSON.stringify(msg) +
                "}}))")
        // #endif
},

sdk.js 页面

const scanCode = (obj = {}) => {
	let params = {
		scanType: ['qrCode'],
		onlyFromCamera: true,
		...obj
	}
	return new Promise((resolve, reject) => {
		uni.scanCode({
			...params,
			success: function(res) {
				resolve(res)
			},
			fail(err) {
				reject(err)
			}
		});
	})
}

export default {
    scanCode
}
  1. H5 Sdk开发,sdk说实话就是一个类而已,里面封装了一系列的方法

如果你这个sdk是第三方通过script标签引入的话,可以考虑一下 将wv.js页面源码下载下来,放在这个类最前面

如果是直接给这个js文件给第三方用,则wv.js通过异步引入也可以

// 第一种
*** uni.webview.1.5.2.js  ***
xxxxxx
*** uni.webview.1.5.2.js  ***

class SDK {
}

// 第二种
*** uni.webview.1.5.2.js  ***
xxxxxx
*** uni.webview.1.5.2.js  ***

class SDK {
    constructor() {
        this.LoadUniJS()
    }
    LoadUniJS = async () => {
    return new Promise((resolve, reject) => {
      if (window.uni) {
        resolve(window.uni);
      } else {
        const script = document.createElement("script");
        script.src =
          "https://js.cdn.aliyun.dcloud.net.cn/dev/uni-app/uni.webview.1.5.2.js";
        script.type = "text/javascript";
        script.async = true;
        script.onerror = reject;
        document.body.appendChild(script);
        script.onload = () => {
          resolve(window.uni);
        };
      }
    });
  };
}

下面写下第一种方式的

*** uni.webview.1.5.2.js  ***
xxxxxxxxxxx
*** uni.webview.1.5.2.js  ***


class UniService {
  VALUE = null; // 调取uni服务后回来的值
  UNI = window.uni; // 保存uni的值
  Dep = {}; // 调度中心,状态变更通知更新
  // 缓存,主要是进入h5页面立即要更新的话,用它缓存下来 等uniapp webView可通信后执行
  // 因为一开始调用的时候,webview还没加载完 通信是不成功的
  cache = []; 
  constructor() {
    // 监听Uni向H5发送结果的回调
    this._ListenDocument()
    // 监听可通信后的回调,主要用于cache缓存的执行
    this._ListenUniLinkH5()
  }

  /**
   * 要调用uni的方法
   * @type {*} 你sdk.js里面写的方法名
   * @param {*} params 参数 
   */
  subscribe = (type, params) => {
    if (!type) {
      throw new Error("_Subscribe需要接受一个type属性进行注册");
    }
    //  默认从参数中解构出成功与失败的回调,待响应结果之后进行推送
    let { success, fail, ...argsObj } = params;

    if (!argsObj) argsObj = {};

    success =
      success && typeof success === "function" ? success : () => this.VALUE;
    fail = fail && typeof fail === "function" ? fail : () => this.VALUE;

    // 注册该类型的属性
    !this.Dep[type] && (this.Dep[type] = []);

    // 默认0 是成功的回调  1是失败的回调
    this.Dep[type].push(success);
    this.Dep[type].push(fail);
    this.SendToUni(type, argsObj);
  };

  /**
   *  获取Uni传输过来的数据
   * @param {*} type uni服务类型
   * @param {*} params 参数
   * @returns null
   */
  receiveUniInfo = (e) => {
    this.VALUE = e.detail.msg || {};
    let { status = "fail", type } = this.VALUE;

    if (!this.Dep[type]) return;
    if (iswStatus === "success") {
      this.Dep[type][0](this.VALUE);
    }
    if (iswStatus === "fail") {
      this.Dep[type][1](this.VALUE);
    }
    this.Dep[type] = [];
  };

  /**
   * 通信:发送信息给uni
   * @param {*} type uni服务类型
   * @param {*} params 参数
   * @returns null
   */
  SendToUni = (type, params) => {
    let serviceUni = this.UNI || window.uni;
    this.VALUE = null;
    serviceUni.postMessage({
      data: {
        type,
        params,
      },
    });
  };

  /**
   * 这个是如果一开始调用的话 则将它加入到缓存中
   */
  setInitCallBack = (cbArr) => {
    if(cbArr && Array.isArray(cbArr)) {
      cbArr.forEach(el => {
        if(typeof el === 'function') {
          this.cache.push(el)
        }
      })
    }
  }

  /**
   * 通信:监听document getUNIMsg
   * @param {*} type uni服务类型
   * @param {*} params 参数
   * @returns null
   */
  _ListenDocument = () => {
    document.addEventListener("noticeH5", this.receiveUniInfo, false);
  };


  /**
   * 通信:监听 通信桥梁是否搭建完成
   * @returns null
   */
  _ListenUniLinkH5 = () => {
    document.addEventListener('UniAppJSBridgeReady', this._UniAppJSBridgeReady, false)
  }


  /**
   * 缓存钩子,有则取出来执行
   * @returns null
   */
  _UniAppJSBridgeReady = () => {
    if (this.cache.length) {
      this.cache.forEach(el=>{
        el();
      })
      this.cache = [];
    }
  }
}




(function () {
  if (!window.UniService) {
    window.UniService = new UniService();
  }
})(window);

上述代码都有解析,这边做一个总结:

  1. 首先在uni页面开发好sdk.js,里面封装uni服务,例如uni.scan扫码服务,然后在webview页面监听H5的消息,接收消息的函数message中主要接收两个值,一个是sdk中的方法名,一个是传给这个方法的参数
  2. 在接收到H5的需求后,webview页面的message函数去sdk中提取出对应的方法执行,然后把执行完的数据通过postH5Mes中自定义的noticeH5去发送消息
  3. H5中通过UniService中的receiveUniInfo 可以获取到uni发送过来的消息,再对应执行调度中心dep中的回调

以下是注意事项

  1. subscribe该方法是订阅uni sdk.js中方法的函数,在该方法中,我会缓存成功和失败的回调到调度中心dep,等uni服务执行完后,我会取出该次服务下对应的成功失败回调,

let { success, fail, ...argsObj } = params;

!this.Dep[type] && (this.Dep[type] = []);

使用方法如下:

// 因为我是挂在window下,所以可以通过window去引用了

UniService.subscribe('sdk中的方法名', {
    success:(e)=>{
    
    },
    fail:(e)=>{
    }
})

如果有什么不懂可以评论,我也是刚接触,文字功底很浅,有什么建议也可以写给弟弟听这是刚完成的第一版,后续有更新会更新

转载自:https://juejin.cn/post/7230737419842666556
评论
请登录