Vue安卓异步请求效率优化方案(基于WebSocket)

一. 应用场景

安卓app的构建方式是由前端页面嵌套在后端安卓外壳中,通过将接口方法配置到window全局对象中,实现前端请求后端提供的接口,后端也可调用前端定义的方法。

主要缺点:

接口无法异步执行,由于JS单线程机制,堵塞UI渲染,影响操作体验。

例子:

  1. 页面切换速度缓慢,等待接口返回后再执行;
  2. loading虽然在接口请求前就设置显示了,但实际效果就是在接口请求完成时才一闪而过;
  3. 无法实现动态进度条等效果。

二. WebSocket主体配置(消息发送+结果接收+心跳机制)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
// store/modules/websocket.js
import { nanoid } from 'nanoid'
import { isValuable } from '@/utils/util'
export default {
state: {
websocket: null, // 建立的连接
lockReconnect: false, // 是否真正建立连接
pingTimeout: 15000, // 15秒一次心跳
pingTimeoutObj: null, // 心跳倒计时
serverTimeoutObj: null, // 心跳超时倒计时
reTimeoutNum: null, // 断开重连倒计时
callbackFunc: {}, // websocket callback函数
apiName: {}, // 唯一码对应的接口名称
timeObj: {} // 接口请求时间
},
mutations: {
// 初始化websocket
webSocketInit(state) {
// 创建一个websocket对象【发送、接收、关闭socket都由这个对象操作】
state.websocket = new WebSocket('ws://127.0.0.1:3200')
state.websocket.onopen = res => {
console.log("Connection success...", res)
// 启动心跳检测
this.commit('webSocketStart')
}
state.websocket.onmessage = res => {
const { uniqueType, context } = JSON.parse(res.data)
if (uniqueType === 'ping' && context === 'pong') {
// 收到服务器信息,心跳重置
this.commit('webSocketReset')
console.log("socket-pong")
} else {
// 打印接口请求时长
console.log(state.apiName[uniqueType], `${(Date.now() - state.timeObj[uniqueType]) / 1000}秒`)
delete state.apiName[uniqueType]
delete state.timeObj[uniqueType]
// 处理接口数据
this.commit('webSocketCallApi', { uniqueType, context })
}
}
state.websocket.onclose = res => {
console.log("Connection closed...", res)
// 重连
this.commit('webSocketReconnect')
}
state.websocket.onerror = res => {
console.log("Connection error...", res)
// 重连
this.commit('webSocketReconnect')
}
},
// 发送消息请求
webSocketSend(state, { apiUrl, context, callback = null}) {
if (state.websocket && state.websocket.readyState === 1) {
// 每个请求都有唯一的id
const uniqueType = nanoid()
const c = isValuable(context) ? typeof context === 'string' ? context : JSON.stringify(context) : null
const data = JSON.stringify(
{
uniqueType,
apiUrl,
context: c
}
)
console.log('query data', data)
state.apiName[uniqueType] = apiUrl
state.timeObj[uniqueType] = Date.now()
state.websocket.send(data)
if (callback) state.callbackFunc[uniqueType] = callback
}
},
// 处理请求回调
webSocketCallApi (state, { uniqueType, context }) {
if (uniqueType === 'ping') return
const res = JSON.parse(context)
if (state.callbackFunc[uniqueType]) {
state.callbackFunc[uniqueType](res)
this.commit('setWsCallBackFunc', { uniqueType, callback: null })
}
},
// 回调函数设置
setWsCallBackFunc (state, data) {
if (data.callback === null) {
delete state.callbackFunc[data.uniqueType]
} else {
state.callbackFunc[data.uniqueType] = data.callback
}
},
// 重新连接
webSocketReconnect(state) {
if (state.lockReconnect) {
return
}
state.lockReconnect = true
// 没连接上会一直重连,5秒重试请求重连,设置延迟避免请求过多
state.reTimeoutNum && clearTimeout(state.reTimeoutNum)
state.reTimeoutNum = setTimeout(() => {
// 新连接
this.commit('webSocketInit')
state.lockReconnect = false
}, 5000)
},
// 重置心跳
webSocketReset(state) {
// 清除时间
clearTimeout(state.pingTimeoutObj)
clearTimeout(state.serverTimeoutObj)
// 重启心跳
this.commit('webSocketStart')
},
// 开启心跳
webSocketStart(state) {
state.pingTimeoutObj &&
clearTimeout(state.pingTimeoutObj)
state.serverTimeoutObj &&
clearTimeout(state.serverTimeoutObj)
state.pingTimeoutObj = setTimeout(() => {
// 这里发送一个心跳,后端收到后,返回一个心跳消息,
if (state.websocket.readyState === 1) {
// 如果连接正常
const ping = JSON.stringify({
uniqueType: 'ping',
context: 'ping'
})
state.websocket.send(ping)
} else {
// 否则重连
this.commit('webSocketReconnect')
}
this.serverTimeoutObj = setTimeout(() => {
// 超时关闭
state.websocket.close()
}, 60000)
}, state.pingTimeout)
},
},
actions: {
webSocketSend({ commit }, obj, func) {
commit('webSocketSend', obj, func)
}
}
}

三. 不同请求类型的具体应用

主要参数:

  1. apiUrl:接口地址
  2. context:请求参数
  3. callback:回调方法

(1)同步执行(依赖接口结果)

1
2
3
4
5
6
7
const apiUrl = 'class.updateGroupRemark'
this.$store.dispatch('webSocketSend', { apiUrl, context: this.remarkQuery, callback: r => {
if( r.code === 0 ) {
this.dialogVisible = false
this.$emit('refresh')
}
}})

(2)异步执行(结果互不影响)

1
2
3
4
5
6
7
8
9
const api1 = 'quickSync.initExpData'
this.$store.dispatch('webSocketSend', { apiUrl: api1, callback: r => {
this.asyncResultHandler(api1, r)
}})

const api2 = 'quickSync.initTagValueData'
this.$store.dispatch('webSocketSend', { apiUrl: api2, callback: r => {
this.asyncResultHandler(api2, r)
}})