写一个订阅模式,实现UniApp与H5之间的互相通信
首先,项目是混合app即uniapp做框架,内容八成是H5,所以通信就成了重中之重了
因为我也是第一次接触app端开发,而且uniapp开发不如vue熟悉,其中就带有很多疑惑点了
- 用uni开发打包成app的话,那还能用uni的方法嘛,比如uni.scan之类的 (因为之前我直接用uni开发的小程序打包成app,打开后页面一篇空白)
- 打包成app要那些步骤,怎么搞。
后来重新搭建uni项目后,发现我真的多虑了,很多时候时间一下答案就出来了
- 对于第一点回答,你尽管用uni的方法即可,人家内部会处理好这些的啦~
- 打包app的话,安卓和ios都不一样的,下面介绍两种方式
- 第一种 去dcloud官网 -> 我的应用 -> 点击应用名称 -> Android证书 -> 然后点生成 如图
- 第二种 让后端给你搞一个自定义证书,比较麻烦,不过不用我们动手比较舒服(好像要用as去生成)
- 至于IOS 我这边刚开发,账号都没申请下来,等到那一天我再补充这个坑
其他坑末尾补充
到了通信这块了,虽然通信都是用uni.webview.js (下面统称wv.js) 来实现的,但是网上参次不齐的资料,写法多数有问题,不排除我的问题(我全抄了都出错),下面说说我的写法
因为是混合app,H5页面不止我一方,还有其他部门第三方等接入的,类似小程序接入H5页面一样,H5需要使用到wx.jssdk提供的方法例如扫码跳转等方法,因为不是在微信环境,所以sdk需要我们来写一份实现
实现过程
再该业务情景下,我们需要提供一个方法给第三方,让他调我们的方法,然后把方法结果传回去给他
- 使用 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
}
- 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);
上述代码都有解析,这边做一个总结:
- 首先在uni页面开发好sdk.js,里面封装uni服务,例如uni.scan扫码服务,然后在webview页面监听H5的消息,接收消息的函数message中主要接收两个值,一个是sdk中的方法名,一个是传给这个方法的参数
- 在接收到H5的需求后,webview页面的message函数去sdk中提取出对应的方法执行,然后把执行完的数据通过postH5Mes中自定义的noticeH5去发送消息
- H5中通过UniService中的receiveUniInfo 可以获取到uni发送过来的消息,再对应执行调度中心dep中的回调
以下是注意事项
- 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