微信小程序中使用WebSocket通信
一、在utils文件夹下新建websocket.js文件,用来封装websocket的连接以及生命周期非法:
const app = getApp()
// 域名地址(项目实地址)
const Host = 'wss://fczd.hkbtwx.com/websocket/ws/';
// Socket连接成功
var socketOpen = false;
// Socket关闭
var socketClose = false;
// 消息队列
var socketMsgQueue = [];
// 判断心跳变量
var heart = null;
// 心跳失败次数
var heartBeatFailCount = 0;
// 终止心跳
var heartBeatTimeout = null;
// 终止重连
var connectSocketTimeout = null;
var webSocket = {
// 连接Socket
connectSocket:function(options) {
if (socketOpen) return
// wx.showLoading({
// title: 'Socket连接中...',
// mask: true
// });
socketOpen = false;
socketClose = false;
socketMsgQueue = [];
let url = Host + app.globalData.userInfo.code
wx.connectSocket({
url: url,
header:{
'content-type': 'application/json'
},
success:function(res) {
console.log('链接成功')
if (options) {
options.success && options.success(res);
}
},
fail:function(res) {
if (options) {
options.fail && options.fail(res);
}
}
})
},
// 发送消息
sendSocketMessage:function(options) {
if (socketOpen) {
wx.sendSocketMessage({
data: options.msg,
success: function(res) {
if (options) {
options.success && options.success(res);
}
},
fail: function(res) {
if (options) {
options.fail && options.fail(res);
}
}
})
} else {
socketMsgQueue.push(options.msg)
}
},
// 关闭Socket
closeSocket: function(options) {
if (connectSocketTimeout) {
clearTimeout(connectSocketTimeout);
connectSocketTimeout = null;
};
socketClose = true;
this.stopHeartBeat();
wx.closeSocket({
success: function(res) {
if (options) {
options.success && options.success(res);
}
},
fail: function(res) {
if (options) {
options.fail && options.fail(res);
}
}
})
},
// 收到消息
onSocketMessageCallback: function(msg) {},
// 开始心跳
startHeartBeat: function() {
heart = true;
this.heartBeat();
},
// 正在心跳
heartBeat: function() {
var that = this;
if (!heart) {
return;
};
that.sendSocketMessage({
msg: JSON.stringify({
// 与后端约定,传点消息,保持链接
'message': 'ping',
'toUserId': app.globalData.userInfo.code
}),
success: function(res) {
if (heart) {
heartBeatTimeout = setTimeout(() => {
that.heartBeat();
}, 8000);
}
},
fail: function(res) {
if (heartBeatFailCount > 2) {
that.connectSocket();
};
if (heart) {
heartBeatTimeout = setTimeout(() => {
that.heartBeat();
}, 8000);
};
heartBeatFailCount++;
}
});
},
// 结束心跳
stopHeartBeat: function() {
heart = false;
if (heartBeatTimeout) {
clearTimeout(heartBeatTimeout);
heartBeatTimeout = null;
};
if (connectSocketTimeout) {
clearTimeout(connectSocketTimeout);
connectSocketTimeout = null;
}
}
};
// 监听WebSocket打开连接
wx.onSocketOpen(function(res) {
wx.hideLoading();
// 如果已经关闭socket
if (socketClose) {
webSocket.closeSocket();
} else {
socketOpen = true
for (var i = 0; i < socketMsgQueue.length; i++) {
webSocket.sendSocketMessage(socketMsgQueue[i])
};
socketMsgQueue = []
webSocket.startHeartBeat();
}
});
// 监听WebSocket错误
wx.onSocketError(function(res) {
console.log('WebSocket连接打开失败,请检查!', res);
wx.hideLoading();
// wx.showToast({
// title: 'Socket连接失败:' + JSON.stringify(res),
// icon: 'none',
// duration: 3000
// })
});
// 监听WebSocket接受到服务器的消息
wx.onSocketMessage(function(res) {
console.log(res.data)
webSocket.onSocketMessageCallback(res.data);
});
// 监听WebSocket关闭连接后重连
wx.onSocketClose(function(res) {
if (!socketClose) {
clearTimeout(connectSocketTimeout);
connectSocketTimeout = setTimeout(() => {
webSocket.connectSocket();
}, 10000);
}
});
module.exports = webSocket;
二、在需要接收websocket消息的页面引入:
const app = getApp()
const $api = require("../../utils/api.js")
const myRequest = require('../../utils/request.js')
let navBarTitleText = ''
const WebSocket = require('../../utils/websocket.js')
Page({
data: {
isBindWx: false,
showLoginPop: false,
userInfo: {},
statisticsData: {}, //统计数据
avatarUrl: '',
// 登录页面相关参数
avHeight: '',
navTop: '',
navHeight: '',
loading: false,
isOut: false,
isLogin: false,
message: '',
messageCode: ''
},
onLoad: function(options) {
if (wx.getStorageSync('isOut')) {
this.setData({
isOut: true
})
wx.setStorageSync('isOut', '')
}
},
onShow: function() {
if (typeof this.getTabBar === 'function' && this.getTabBar()) {
this.getTabBar().setData({
selected: 0,
show: app.globalData.isLogin
})
}
// 打开调试
wx.setEnableDebug({
enableDebug: false
})
wx.setNavigationBarTitle({
title: navBarTitleText
})
this.setData({
isBindWx: app.globalData.isBindWx,
userInfo: app.globalData.userInfo,
avatarUrl: app.globalData.avatarUrl,
navHeight: app.globalData.navHeight,
navTop: app.globalData.navTop,
loading: false,
isLogin: app.globalData.isLogin,
navBarTitleText: app.globalData.isLogin ? '首页' : '登录'
})
if (app.globalData.isLogin) {
this.getStatistics()
}
// 设置接收消息回调
WebSocket.onSocketMessageCallback = this.onSocketMessageCallback
},
// 用户信息
onUserInfo() {
let that = this
if (app.globalData.avatarUrl) {
wx.navigateTo({
url: '/pages/userInfo/index'
})
return
}
wx.getUserProfile({
desc: '用于展示用户信息',
success: (res) => {
that.setData({
avatarUrl: res.userInfo.avatarUrl
})
app.globalData.avatarUrl = res.userInfo.avatarUrl
wx.navigateTo({
url: '/pages/userInfo/index'
})
console.log('---------------' + res.userInfo.nickName)
that.uploadPhoto(that.data.avatarUrl, res.userInfo.nickName)
}
})
},
// 上传微信头像
uploadPhoto(url, nickName) {
myRequest.request({
url: $api.baseUrl + $api.uploadPhoto,
method: 'POST',
data: {
photo: url,
wxName: nickName,
way: '1'
},
success: res => {
if (res.data.code != '0') {
wx.showToast({
title: res.data.msg,
duration: 3000,
icon: 'none'
})
}
}
})
},
// 订阅消息
onSubscribeMessage() {
wx.requestSubscribeMessage({
tmplIds: ['hkKPWSfweqkac20HAD2rlvweeV6vsuNysxKqibtECl8', 'eJ5j3aLYIO_rRax1dcKPrIaXAz99kmdxurSYX29CDbY'],
complete: messageData => {
app.globalData.isFirstOperation = false
wx.showToast({
title: '消息订阅成功',
duration: 2000,
icon: 'success'
})
}
})
},
// 拒绝绑定微信
onRefuse() {
this.setData({
showLoginPop: false
})
},
// 同意绑定微信
onAgree() {
let that = this
wx.login({
success(res) {
if (res.code) {
// todo 发送 res.code 到后台换取 openId, sessionKey, unionId
app.globalData.isBindWx = true
that.setData({
showLoginPop: false,
isBindWx: true
})
} else {
wx.showToast({
title: '登录失败!' + res.errMsg,
duration: 3000,
icon: 'none'
})
}
}
})
},
/**
* 登录相关
*/
loginSucccess(e) {
this.getTabBar().setData({
show: true
})
// 创建WebSocket连接
WebSocket.connectSocket()
if (this.data.isOut) {
wx.switchTab({
url: '/pages/news/index'
})
this.setData({
isOut: false
})
} else {
this.setData({
loading: false,
isLogin: true,
userInfo: e.detail,
navBarTitleText: '首页'
})
// 获取数据
// sthis.getStatistics()
wx.setNavigationBarTitle({
title: '首页'
})
}
},
// Socket收到的信息
onSocketMessageCallback: function(res) {
let message = JSON.parse(res)
if (message.message == '连接成功' || message.message == 'ping') return
this.setData({
message: message.message,
messageCode: message.code
})
setTimeout(() => {
this.setData({
message: '',
messageCode: ''
})
}, 10000)
},
// 页面销毁时关闭连接
onUnload: function(options) {
WebSocket.closeSocket();
}
})
三、以上便是微信小程序中WebSocket的封装和使用;下面再介绍单Activity多Fragment的Android项目中使用WebSocket进行通信:
3.1 编写WebSocket工具类:
public class MyWsManager {
private static final String URL = SystemConst.WS_URL + "?appKey=" + CommonUtils.getDeviceNumber();
private static MyWsManager myWsManager;
private static WsManager wsManager;
protected Logger logger = Logger.getLogger(MyWsManager.class);
private Handler.Callback wsConnectCallBack;
private static final int WS_OPEN_STATUS = 1;
private static final int WS_MESSAGE_STATUS = 2;
private static final int WS_MESSAGE_2_STATUS = 3;
private static final int WS_RECONNECT_STATUS = 4;
private static final int WS_CLOSING_STATUS = 5;
private static final int WS_CLOSED_STATUS = 6;
private static final int WS_FAILURE_STATUS = 7;
public static MyWsManager getInstance() {
if (myWsManager == null) {
synchronized (MyWsManager.class) {
if (myWsManager == null) {
myWsManager = new MyWsManager();
}
}
}
return myWsManager;
}
public void setWsConnectCallBack(Handler.Callback wsCallBack){
if (wsCallBack != null){
wsConnectCallBack = wsCallBack;
}
}
private void onCallBack(Message message){
if (wsConnectCallBack != null){
wsConnectCallBack.handleMessage(message);
}
}
public void initWS(Context context) {
try {
wsManager = new WsManager.Builder(context).client(
new OkHttpClient().newBuilder()
.pingInterval(15, TimeUnit.SECONDS)
// .retryOnConnectionFailure(true)
.build())
.needReconnect(true)
.wsUrl(URL)
.build();
wsManager.setWsStatusListener(wsStatusListener);
wsManager.startConnect();
} catch (Exception e) {
logger.error("WebSocket连接异常:" + e.getMessage());
}
}
//状态监听
private WsStatusListener wsStatusListener = new WsStatusListener() {
Message message = new Message();
@Override
public void onOpen(Response response) {
logger.info("WebSocket服务器连接成功");
EventBus.getDefault().postSticky("connect");
message.what = WS_OPEN_STATUS;
onCallBack(message);
}
@Override
public void onMessage(String text) {
message.what = WS_MESSAGE_STATUS;
message.obj = text;
onCallBack(message);
}
@Override
public void onMessage(ByteString bytes) {
message.what = WS_MESSAGE_2_STATUS;
message.obj = bytes.toString();
onCallBack(message);
}
@Override
public void onReconnect() {
// logger.debug("WebSocket服务器重连接中...");
message.what = WS_RECONNECT_STATUS;
onCallBack(message);
}
@Override
public void onClosing(int code, String reason) {
// logger.debug("WebSocket服务器连接关闭中:" + reason);
message.what = WS_CLOSING_STATUS;
onCallBack(message);
//上面提及了设备会出现断开后无法连接的情况,那这种无法连接的情
//况我发现有可能会卡在这个关闭过程中,因为如果是确实断开后会确实的启动重连机制
//这里主要的目的就死让他跳出这个关闭中的状态,确实的关闭了ws
if (wsManager != null) {
wsManager.stopConnect();
wsManager.startConnect();
}
}
@Override
public void onClosed(int code, String reason) {
logger.debug("WebSocket服务器连接已关闭:" + reason);
message.what = WS_CLOSED_STATUS;
onCallBack(message);
}
@Override
public void onFailure(Throwable t, Response response) {
logger.error("WebSocket服务器连接失败:" + t.getMessage());
message.what = WS_FAILURE_STATUS;
onCallBack(message);
}
};
//发送ws数据
public void sendData(String content) {
if (wsManager != null && wsManager.isWsConnected()) {
boolean isSend = wsManager.sendMessage(content);
if (isSend) {
logger.info("WebSocket发送数据成功");
} else {
logger.error("WebSocket发送数据失败");
}
} else {
ToastUtils.showToast("WebSocket连接已断开");
}
}
//断开ws
public void disConnect() {
if (wsManager != null)
wsManager.stopConnect();
}
//判断WS是否断开了
public boolean wsIsConnect(){
if (wsManager == null) {
return false;
}
return wsManager.isWsConnected();
}
}
3.2 在activity中初始化WebSocket的连接、接收WebSocket消息,处理后分发给对应的fragment:
在processLogic中初始化WebSocket的连接
private void initWebSocket() {
MyWsManager.getInstance().initWS(this);
time = new TimeCount(2000, 1000);
//WS状态监听
MyWsManager.getInstance().setWsConnectCallBack(new Handler.Callback() {
@Override
public boolean handleMessage(@NonNull Message message) {
switch (message.what) {
case 1: //连接成功
timerCount = 1;
time.cancel();
homeModel.websocketStatus.set(1);
break;
case 2: //接收string类型数据
String data = (String) message.obj;
if (!data.equals("连接建立成功")){
showWebSocketData(data);
}
break;
case 3: //接收ByteString类型数据
String data2 = (String) message.obj;
break;
case 4: //websocket连接中
break;
case 5: //连接关闭中
break;
case 6: //连接已关闭
case 7: //连接失败
if (time != null){
time.cancel();
time.start();
}
homeModel.websocketStatus.set(0);
break;
}
return false;
}
});
}
// 重连机制
public class TimeCount extends CountDownTimer {
public TimeCount(long millisInFuture, long countDownInterval) {
super(millisInFuture, countDownInterval);
}
@Override
public void onTick(long millisUntilFinished) { // 计时过程
}
@Override
public void onFinish() {// 计时完毕
isTimer = false;
//判断WebSocket是否断开
if (!MyWsManager.getInstance().wsIsConnect()) {
MyWsManager.getInstance().disConnect(); //断开socket
MyWsManager.getInstance().initWS(MyApplication.getContext());
}
}
}
3.2 处理WebSocket消息,分发给对应的fragment:
private WebsocketPushBean data = null;
private void showWebSocketData(String pushData) {
try {
data = JSON.parseObject(pushData, WebsocketPushBean.class);
} catch (Exception e){
logger.error("WebSocket推送数据解析失败");
}
if (data != null){
if (data.getType() == 6 || data.getType() == 8){ //6-主扫支付
fragment.setWebSocketData(data);
}else if (data.getType() == 7) { //主扫跳主动支付
websocketPushBeans.add(data);
fragment.setWebSocketData(data);
payPopupView();
} else if (data.getType() == 2){ //主动支付
websocketPushBeans.add(data);
fragment.setWebSocketData(data);
payPopupView();
} else if (data.getType() == 1){ //网约车支付
websocketAutoPayBeans.add(data);
autoPayPopupView();
} else if (data.getType() == 9){ //车牌支付
fragment.setWebSocketData(data);
} else if (data.getType() == 10) { //流水绑定
fragment.setWebSocketData(data);
} else { //换购、赠品提示
websocketPushBeans.add(data);
payPopupView();
}
}
}
3.3 activity销毁时断开WebSocket连接
@Override
protected void onDestroy() {
super.onDestroy();
EventBus.getDefault().unregister(this);
MyWsManager.getInstance().disConnect();
if (time != null){
time.cancel();
time = null;
}
}
3.4 项目中那么多fagmen,上面的fagmen指哪个?因为所有的fagmenl都继承BaseFragment,所有上面的fagmen是BaseFragment,至于如何确认是哪个具体的fagment,后面会说明。
3.5 创建一个IBusinessInterface接口,用来把当前显示的fragment设置给activity,内容如下:
public interface IBusinessInterface {
void setSelectedFragment(BaseFragment fragment);
}
3.6 Activity实现此接口,并把传递过来的fragment设置给BaseFragment:
@Override
public void setSelectedFragment(BaseFragment fragment) {
this.fragment = fragment;
}
3.7 fragment显示到前台时,把当前fragment设置给activity:
IBusinessInterface backInterface = (IBusinessInterface)getActivity();
backInterface.setSelectedFragment(this); // 将fragment传递到Activity中
至此,activity中的fragment便确认了。
3.8 fragment中重写接收WebSocket消息的方法setWebSocketData:
/**
* 接收Websocket返回的数据
*/
public void setWebSocketData(WebsocketPushBean data){
if (data.getType() == 6 || data.getType() == 7){ //支付成功
if (customPopupView != null){
customPopupView.dismiss();
}
vm.getOrderInfoByFlowNo(data.getFlowNo());
} else if (data.getType() == 2){ //主动支付
if (beScanPopupView != null){
beScanPopupView.dismiss();
}
if (customPopupView != null){
customPopupView.dismiss();
}
} else if (data.getType() == 8){ //扫码开票、扫码兑换关闭弹框
if (customPopupView != null && customPopupView.isShow()){
customPopupView.dismiss();
}
currentPage = 1;
oilGunPage = 1;
lastPage = false;
oilWaterData.clear();
//获取当前选中的油枪信息
vm.getOrderInfoCountPos(model.deptCode.get(), model.selectGunList.get(), "1");
//获取油品流水信息
vm.getOrderInfoAndPayFlagPos(model.deptCode.get(), model.selectGunList.get(), 1);
} else if (data.getType() == 9){ //车牌支付
if (customPopupView != null){
customPopupView.dismiss();
}
model.flowNo.set(data.getFlowNo());
vm.getOrderInfoByFlowNo(data.getFlowNo());
}
}
至此,activity接收WebSocket消息、分发给对应的fragment、fragment处理对应的业务整个链路已经通了。