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

使用FCM的React Native应用,如何让后端知晓设备FCM服务已关闭?

React Native中检测应用被杀死状态并通知后端的方案

React Native没有统一的跨平台API能直接捕获应用被用户/系统杀死的事件,因iOS和Android系统机制差异极大,需针对双平台分别处理,同时可结合通用兜底方案。

Android 平台实现方式

Android提供了多个可检测应用终止场景的原生API,可通过React Native桥接或直接在原生层实现:

1. 重写Application类的onTaskRemoved方法

当用户从最近任务列表移除应用时,系统会触发该方法。需使用WorkManager或JobIntentService执行网络请求,避免进程被立即终止导致请求失败。

示例原生代码(Kotlin):

class MyApplication : Application() {
    override fun onTaskRemoved(rootIntent: Intent?) {
        super.onTaskRemoved(rootIntent)
        // 用WorkManager调度通知后端的任务
        val workRequest = OneTimeWorkRequestBuilder<NotifyBackendWorker>().build()
        WorkManager.getInstance(this).enqueue(workRequest)
    }
}

class NotifyBackendWorker(context: Context, params: WorkerParameters) : Worker(context, params) {
    override fun doWork(): Result {
        return try {
            // 发送POST请求告知后端应用被移除
            val url = URL("https://your-backend/api/app-killed")
            val conn = url.openConnection() as HttpURLConnection
            conn.requestMethod = "POST"
            conn.setRequestProperty("Content-Type", "application/json")
            conn.doOutput = true
            val outputStream = conn.outputStream
            outputStream.write("{\"deviceId\":\"your-device-id\"}".toByteArray())
            outputStream.flush()
            conn.responseCode
            Result.success()
        } catch (e: Exception) {
            Result.retry()
        }
    }
}

2. 监听进程生命周期

通过ProcessLifecycleOwner监听应用生命周期,进入DESTROYED状态时触发通知:

ProcessLifecycleOwner.get().lifecycle.addObserver(object : DefaultLifecycleObserver {
    override fun onDestroy(owner: LifecycleOwner) {
        super.onDestroy(owner)
        // 同样通过WorkManager执行请求
    }
})

iOS 平台实现方式

iOS对后台进程限制严格,无法可靠捕获应用被杀死的事件(系统会直接终止进程,无执行代码的时间窗口),但可通过以下方式尽可能处理:

1. 利用后台任务

应用进入后台时启动短时间后台任务,尝试通知后端;也可结合BGTaskScheduler执行更长时间任务(需在Info.plist配置权限):

示例原生代码(Swift):

func applicationDidEnterBackground(_ application: UIApplication) {
    var bgTask: UIBackgroundTaskIdentifier = .invalid
    bgTask = application.beginBackgroundTask(withName: "NotifyBackend") {
        application.endBackgroundTask(bgTask)
        bgTask = .invalid
    }
    
    DispatchQueue.global().async {
        // 发送请求到后端
        let url = URL(string: "https://your-backend/api/app-background")!
        var request = URLRequest(url: url)
        request.httpMethod = "POST"
        request.httpBody = "{\"deviceId\":\"your-device-id\"}".data(using: .utf8)
        let task = URLSession.shared.dataTask(with: request) { _, _, _ in
            application.endBackgroundTask(bgTask)
            bgTask = .invalid
        }
        task.resume()
    }
}

2. 心跳兜底方案

由于iOS无法100%检测应用被杀死,推荐前端定期给后端发送心跳请求,后端若超过设定时间(如5分钟)未收到心跳,标记该设备对应的应用为离线状态。

跨平台通用兜底方案:心跳机制

无论iOS还是Android,心跳机制都是最可靠的离线判断方式,不受系统限制:

React Native中实现定时心跳:

import { AppState, Platform } from 'react-native';

let heartbeatInterval;

const startHeartbeat = () => {
    // 每隔3分钟发送一次心跳
    heartbeatInterval = setInterval(async () => {
        try {
            await fetch('https://your-backend/api/heartbeat', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                },
                body: JSON.stringify({ deviceId: 'your-device-id' }),
            });
        } catch (e) {
            // 网络异常,忽略或按需重试
        }
    }, 3 * 60 * 1000);
};

const stopHeartbeat = () => {
    if (heartbeatInterval) {
        clearInterval(heartbeatInterval);
        heartbeatInterval = null;
    }
};

// 根据AppState启停心跳,iOS后台建议停止,Android后台可继续
AppState.addEventListener('change', (state) => {
    if (state === 'active') {
        startHeartbeat();
    } else if (Platform.OS === 'ios') {
        stopHeartbeat();
    }
});

结合FCM的补充方案

后端发送FCM推送时,若收到NotRegistered响应,说明设备令牌已失效(通常是应用被卸载或长期未启动被系统清理),可直接标记该设备为离线状态。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.27 02:58:28