uni.connectSocket 心跳必须手动管理,需定时发送符合协议的 ping 消息并校验 pong 响应,结合生命周期控制、网络状态检测与平台保活策略确保连接稳定。

uni.connectSocket 心跳必须手动管理,没有自动保活
uni-app 的 uni.connectSocket 不像原生 WebSocket 或 socket.io 那样内置心跳逻辑,连接建立后不发数据,服务端在超时(通常 60s)后会主动断开。你看到的“断开-重连”循环,八成是因为没发心跳,或心跳格式/时机不对。
关键点在于:心跳不是可选项,而是连接存活的必要条件;且必须配合前后台生命周期做开关控制,否则切后台后定时器还在跑,系统会 kill 进程或报错。
- 心跳间隔建议设为
30–45秒,比服务端超时阈值(如60秒)短至少10秒,留出网络抖动余量 - 每次发心跳前,必须检查
socketTask.readyState === 1,否则直接跳过,避免send报错中断后续逻辑 -
onHide中必须调用socketTask.close(),onShow中重新uni.connectSocket并重置心跳定时器 - 首次连接失败不要立刻重试,加
1–3秒退避延迟,防止雪崩式重连
心跳数据格式要和服务端严格对齐
很多“心跳发了但服务端没响应”的问题,根源是双方协议不一致:你发的是字符串 "ping",服务端期待的是 JSON 字符串 {"type":"ping"};或者你传了对象没 JSON.stringify,导致后端解析失败。
统一做法是:始终用字符串发送,且显式序列化:
socketTask.send({ data: JSON.stringify({ type: 'ping', ts: Date.now() }) })
服务端返回也必须是可解析的 JSON,例如 {"type":"pong","ts":171xxxxxx};前端收到后要 JSON.parse(res.data) 再校验 type === 'pong',不能直接字符串比对。
H5 端若混用原生 WebSocket 和 uni.sendSocketMessage,注意前者默认只接受 ArrayBuffer,后者支持 String 或 ArrayBuffer,类型不一致会导致丢包。
心跳定时器必须全局单例 + 状态防抖
多个页面都启动自己的心跳定时器,会导致重复发包、资源浪费甚至冲突。也不能依赖 onHide/onShow 做唯一开关——Android 多任务切换时这两个钩子经常丢失或延迟触发。
实操要点:
- 声明一个全局变量
heartBeatTimer = null,每次启动前先clearInterval(heartBeatTimer),再重新赋值 - 发心跳前先调用
uni.getNetworkType,返回"none"或"unknown"就跳过,避免失败重试雪崩 - 用
uni.setStorageSync('lastHeartbeat', Date.now())记本地时间戳,App 再次启动时比对:如果距上次已超120秒,立刻补发一次心跳,再启动定时器 - 云函数返回
401或 token 过期时,前端必须立即clearInterval(heartBeatTimer)并清空所有相关状态,否则持续报错占资源
iOS/Android 后台心跳不能靠 JS 定时器
真后台下,setInterval 和网络请求大概率被系统休眠或终止。iOS 进入后台 30 秒内无音频/定位等保活权限,心跳必然断;Android 遇到华为/小米等厂商省电策略,JS 层心跳直接失效。
正确做法是降级处理:
- iOS:在
manifest.json的ios节点配"usesBackgroundModes": ["audio"](仅作保活用途) - Android:检测低电量(
uni.getBatteryInfo),低电量时将心跳从30s降频为2min一次 - 真后台场景放弃 JS 定时器,改用
plus.push.addEventListener('click', ...)等原生事件兜底,或由云函数基于lastHeartbeatTime字段做“最后活跃时间”兜底判断 - 云函数中存时间用
new Date().toISOString()(如"2024-05-20T10:23:45.123Z"),别用毫秒数,避免客户端与云函数时钟不同步
最易被忽略的是:心跳成功 ≠ 用户在线。单次失败可能是网络抖动,真正可用的状态得结合 lastHeartbeatTime、isOnline 字段和后端推送的 user_offline 事件共同判定。











