You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Android Firebase游戏APP中实现心跳消息检测用户断开?

基于Firebase Realtime Database的游戏心跳系统实现方案

针对你提到的onDisconnect无法覆盖所有崩溃/强杀场景的问题,下面是一套可落地的Java实现方案,通过前台心跳服务+主机定时检测的组合,完成玩家状态追踪:

一、访客端:前台心跳服务

使用Foreground Service持续发送心跳(避免被系统后台回收),结合onDisconnect做双重保障:

1. 实现心跳服务类

public class HeartbeatService extends Service {
    private static final long HEARTBEAT_INTERVAL = 5000; // 每5秒发送一次心跳
    private DatabaseReference playerRef;
    private String playerId;
    private Handler heartbeatHandler;
    private Runnable heartbeatTask;

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        // 获取传入的玩家ID和游戏代码
        playerId = intent.getStringExtra("PLAYER_ID");
        String gameCode = intent.getStringExtra("GAME_CODE");
        // 定位到当前玩家的数据库节点
        playerRef = FirebaseDatabase.getInstance()
                .getReference("games")
                .child(gameCode)
                .child("players")
                .child(playerId);

        // 启动前台服务,防止被系统回收
        Notification notification = new NotificationCompat.Builder(this, "GAME_HEARTBEAT_CHANNEL")
                .setContentTitle("游戏连接中")
                .setContentText("保持游戏在线状态")
                .setSmallIcon(R.drawable.ic_game)
                .build();
        startForeground(1001, notification);

        // 初始化心跳任务
        heartbeatHandler = new Handler(Looper.getMainLooper());
        heartbeatTask = new Runnable() {
            @Override
            public void run() {
                // 更新数据库中的心跳时间戳
                playerRef.child("lastHeartbeat").setValue(System.currentTimeMillis());
                heartbeatHandler.postDelayed(this, HEARTBEAT_INTERVAL);
            }
        };
        heartbeatHandler.post(heartbeatTask);

        // 绑定OnDisconnect,覆盖正常断开场景
        playerRef.onDisconnect().child("status").setValue("QUIT");

        return START_STICKY;
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        // 停止心跳任务
        if (heartbeatHandler != null && heartbeatTask != null) {
            heartbeatHandler.removeCallbacks(heartbeatTask);
        }
        // 主动标记退出状态
        playerRef.child("status").setValue("QUIT");
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }
}

2. 启动/停止服务

在访客的游戏页面中,进入时启动服务,退出时停止:

// 启动服务(在onResume或进入游戏时调用)
private void startHeartbeatService(String playerId, String gameCode) {
    Intent intent = new Intent(this, HeartbeatService.class);
    intent.putExtra("PLAYER_ID", playerId);
    intent.putExtra("GAME_CODE", gameCode);
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        startForegroundService(intent);
    } else {
        startService(intent);
    }
}

// 停止服务(在onPause或退出游戏时调用)
private void stopHeartbeatService() {
    stopService(new Intent(this, HeartbeatService.class));
}

3. 配置清单

在AndroidManifest.xml中添加权限和服务声明:

<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />

<application ...>
    <service android:name=".HeartbeatService" />
</application>

二、主机端:状态检测逻辑

主机定时遍历所有玩家的心跳时间戳,超过阈值则判定为断开:

1. 实现断开检测任务

private static final long DISCONNECT_THRESHOLD = 15000; // 15秒无心跳判定为断开
private Handler checkHandler;
private Runnable checkTask;
private DatabaseReference playersRef;

// 初始化检测任务(主机进入游戏时调用)
private void initDisconnectCheck(String gameCode) {
    playersRef = FirebaseDatabase.getInstance()
            .getReference("games")
            .child(gameCode)
            .child("players");
    
    checkHandler = new Handler(Looper.getMainLooper());
    checkTask = new Runnable() {
        @Override
        public void run() {
            playersRef.addListenerForSingleValueEvent(new ValueEventListener() {
                @Override
                public void onDataChange(@NonNull DataSnapshot snapshot) {
                    long currentTime = System.currentTimeMillis();
                    for (DataSnapshot playerSnap : snapshot.getChildren()) {
                        String playerId = playerSnap.getKey();
                        String status = playerSnap.child("status").getValue(String.class);
                        
                        // 仅检测活跃状态的玩家
                        if ("ACTIVE".equals(status)) {
                            Long lastHeartbeat = playerSnap.child("lastHeartbeat").getValue(Long.class);
                            if (lastHeartbeat != null && currentTime - lastHeartbeat > DISCONNECT_THRESHOLD) {
                                // 标记为断开,并执行移除逻辑
                                playersRef.child(playerId).child("status").setValue("DISCONNECTED");
                                removePlayerFromGame(playerId); // 自定义移除玩家的业务逻辑
                            }
                        }
                    }
                }

                @Override
                public void onCancelled(@NonNull DatabaseError error) {
                    // 处理数据库读取错误
                }
            });
            // 每7.5秒检查一次(阈值的一半,提升检测灵敏度)
            checkHandler.postDelayed(this, DISCONNECT_THRESHOLD / 2);
        }
    };
    checkHandler.post(checkTask);
}

// 停止检测任务(主机退出游戏时调用)
private void stopDisconnectCheck() {
    if (checkHandler != null && checkTask != null) {
        checkHandler.removeCallbacks(checkTask);
    }
}

三、数据库结构建议

每个游戏节点下的players子节点结构示例:

"games": {
  "game_xxxxxx": {
    "players": {
      "player_123": {
        "status": "ACTIVE",
        "lastHeartbeat": 1700000000000,
        "nickname": "访客A"
      },
      "player_456": {
        "status": "QUIT",
        "lastHeartbeat": 1699999990000,
        "nickname": "访客B"
      }
    }
  }
}

四、额外优化点

  • 主机也需要发送心跳,访客端同样监听主机的心跳状态,超过阈值则执行游戏结束逻辑
  • 心跳间隔和断开阈值可根据游戏实时性需求调整(比如实时对战可以缩小心跳间隔到2秒,阈值5秒)
  • 可以给心跳服务添加通道通知的配置(Android 8.0+需要),避免通知栏提示过于突兀

内容的提问来源于stack exchange,提问作者Francesco

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.24 04:59:53