Firebase onDisconnect仅在玩家重连后生效,如何实现断网即时触发?
解决Firebase onDisconnect断网不即时触发的问题
首先得明确:你用的onDisconnect().setValue(1)本质是客户端预先告知Firebase服务器「如果我和你的连接中断,就执行这个写操作」。但服务器是通过心跳超时判断客户端断联的,默认超时约60秒,所以无法做到即时触发。要实现接近实时的断联检测,得结合客户端心跳+服务器端超时监听的方案。
解决方案步骤
1. 客户端添加心跳机制
客户端定期向数据库写入心跳标记(比如当前时间戳),同时保留onDisconnect作为兜底(处理正常退出、主动断网的场景):
// 初始化玩家心跳引用(替换为你的玩家唯一标识,比如playerId) DatabaseReference heartbeatRef = FirebaseDatabase.getInstance() .getReference("rooms/" + roomName + "/players/" + playerId + "/heartbeat"); // 启动定时任务,每10秒更新一次心跳值 Timer heartbeatTimer = new Timer(); heartbeatTimer.scheduleAtFixedRate(new TimerTask() { @Override public void run() { // 写入当前时间戳作为心跳 heartbeatRef.setValue(System.currentTimeMillis()); } }, 0, 10000); // 立即执行,之后每10秒执行一次 // 断联时移除心跳标记,同时触发playersLeave累加 heartbeatRef.onDisconnect().removeValue(); DatabaseReference leaveRef = FirebaseDatabase.getInstance() .getReference("rooms/" + roomName + "/playersLeave"); // 用transaction替代setValue,避免多玩家断联时数值被覆盖 leaveRef.onDisconnect().transaction(currentValue -> { return (currentValue == null ? 0 : currentValue) + 1; }); // 注意:玩家正常退出游戏时,要取消定时任务 // heartbeatTimer.cancel();
2. 服务器端用云函数监听心跳超时
通过Firebase云函数监听心跳节点的变化,如果超过设定时间(比如20秒)未更新心跳,就判定玩家断联,主动累加playersLeave数值:
const functions = require("firebase-functions"); const admin = require("firebase-admin"); admin.initializeApp(); exports.handlePlayerDisconnect = functions.database .ref("/rooms/{roomId}/players/{playerId}/heartbeat") .onWrite(async (change, context) => { const roomId = context.params.roomId; const playerId = context.params.playerId; const currentTime = Date.now(); // 如果心跳节点被移除(比如onDisconnect触发),直接处理断联 if (!change.after.exists()) { const leaveRef = admin.database().ref(`/rooms/${roomId}/playersLeave`); return leaveRef.transaction(current => (current || 0) + 1); } // 延迟20秒后检查心跳是否更新 return new Promise(resolve => { setTimeout(async () => { const heartbeatSnapshot = await admin.database() .ref(`/rooms/${roomId}/players/${playerId}/heartbeat`) .once("value"); // 如果心跳存在且超时超过20秒,判定为断联 if (heartbeatSnapshot.exists() && currentTime - heartbeatSnapshot.val() > 20000) { const leaveRef = admin.database().ref(`/rooms/${roomId}/playersLeave`); await leaveRef.transaction(current => (current || 0) + 1); // 顺便清理玩家的心跳节点 await heartbeatSnapshot.ref.remove(); } resolve(); }, 20000); }); });
关键细节说明
- 心跳间隔与超时时间:可根据需求调整,比如心跳间隔5秒、超时15秒,实时性更强,但会增加数据库写入次数,需平衡成本与体验。
- transaction的使用:必须用
transaction替代setValue(1),否则多个玩家同时断联时,数值会被覆盖,无法正确累加。 - 定时任务清理:玩家正常退出游戏时,一定要调用
heartbeatTimer.cancel()取消心跳任务,避免无效的数据库写入。
内容的提问来源于stack exchange,提问作者SuperMen
相关产品推荐
相关产品推荐

