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

APP后台/被杀状态下FCM触发定位更新服务的实现方案咨询

问题描述

需求:通过Firebase FCM通知触发启动后台定位更新服务,要求在onMessageReceived(remoteMessage: RemoteMessage)方法中启动该服务。
现状:APP处于前台时,onMessageReceived能正常启动服务并进行定位更新;但APP后台运行或被杀死后,定位服务无法启动,定位更新停止。


现有Firebase Messaging服务代码

@AndroidEntryPoint
class MyFirebaseMessagingService : FirebaseMessagingService() {
    override fun onMessageReceived(remoteMessage: RemoteMessage) {
        super.onMessageReceived(remoteMessage)

    if (remoteMessage.data.isNotEmpty()) {
        Log.d("FirebaseLogs", "Remote message data: ${remoteMessage.data}")
        Intent(this, LocationBackgroundService::class.java).also { intent ->
            intent.action = LocationBackgroundService.ACTION_START
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                startForegroundService(intent)
            } else {
                startService(intent)
            }
        }
    } else {
        Log.d("FirebaseLogs", "Remote message data is empty")
    }
}

override fun onNewToken(token: String) {
    super.onNewToken(token)
    SharedPrefDataStore.init().setValue(this, FCM_TOKEN, token)
}
}

现有定位后台服务代码

@AndroidEntryPoint
class LocationBackgroundService : Service() {

private val serviceScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
private lateinit var locationClient: LocationClient
private lateinit var db: FirebaseFirestore

override fun onBind(intent: Intent?): IBinder? {
    return null
}

override fun onCreate() {
    super.onCreate()
    db = FirebaseFirestore.getInstance()
    locationClient = DefaultLocationClient(
        applicationContext,
        LocationServices.getFusedLocationProviderClient(applicationContext)
    )
    createNotificationChannel()
}

override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
    when(intent?.action){
        ACTION_START -> start()
        ACTION_STOP -> stop()
    }
    //return START_STICKY
    return super.onStartCommand(intent, flags, startId)
}

private fun start() {

    val pendingIntent: PendingIntent =
        Intent(this, MainActivity::class.java).let { notificationIntent ->
            PendingIntent.getActivity(
                this, 0, notificationIntent,
                PendingIntent.FLAG_IMMUTABLE
            )
        }

    val notification = NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID)
        .setContentTitle("Tracking location...")
        .setContentText("")
        .setSmallIcon(R.drawable.we_care_small)
        .setOngoing(true)

    val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager

    locationClient
        .getLocationUpdates(10000L)
        .catch { e -> e.printStackTrace() }
        .onEach { location ->
            val lat = location.latitude.toString()
            val long = location.longitude.toString()
            val geoPoint = GeoPoint(location.latitude, location.longitude)

            saveUserLocation(geoPoint)
            val updatedNotification = NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID)
                .setContentTitle("Tracking location...")
                .setContentText("Location: ($lat, $long)")
                .setSmallIcon(R.drawable.we_care_small)
                .setContentIntent(pendingIntent)
                .setOngoing(true)
                .build()
            notificationManager.notify(1, updatedNotification)
        }
        .launchIn(serviceScope)

    startForeground(1, notification.build())
}

private fun createNotificationChannel() {
    val channel = NotificationChannel(
        NOTIFICATION_CHANNEL_ID,
        NOTIFICATION_CHANNEL_NAME,
        NotificationManager.IMPORTANCE_DEFAULT
    ).apply {
        description = "Channel for location service"
    }
    val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
    manager.createNotificationChannel(channel)
}

private fun stop() {
    stopForeground(true)
    stopSelf()
}

override fun onDestroy() {
    super.onDestroy()
    serviceScope.cancel()
}

private fun saveUserLocation(geoPoint: GeoPoint) {
     db.collection(USER_LOCATION)
        .document(Firebase.auth.currentUser?.uid.toString())
        .update("geo_location", geoPoint)
        .addOnCompleteListener {
            Log.d(TAG, "Save UserLocation from Service :: " + it.isSuccessful)
        }
        .addOnFailureListener {
            Log.d(TAG, "Fail to save UserLocation :: " + it.message)
        }
}

companion object {
    const val ACTION_START = "ACTION_START"
    const val ACTION_STOP = "ACTION_STOP"
}
}

现有Manifest配置

<service
        android:name=".service.LocationBackgroundService"
        android:enabled="true"
        android:foregroundServiceType="location"
        android:exported="false"
        tools:ignore="ForegroundServicePermission" />

解决思路与实现方案

1. 确保FCM消息在后台/被杀状态下触发onMessageReceived

FCM默认对后台/被杀状态的APP,会将带notification字段的消息推送到系统通知栏,不会触发onMessageReceived。必须发送纯数据消息:

  • 消息仅包含data字段,不包含notification字段;
  • 若需展示通知,在onMessageReceived中自行构建本地通知展示。

2. 修复后台服务的稳定性

(1) 修改onStartCommand返回值

当前服务返回默认值,导致被系统回收后无法自动重启。替换为START_STICKY:

override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
    when(intent?.action){
        ACTION_START -> start()
        ACTION_STOP -> stop()
    }
    return START_STICKY // 替换原返回值
}

START_STICKY表示服务被系统杀死后,系统会尝试重新创建服务并调用onStartCommand。

(2) 适配Android 12+后台启动限制

  • 申请POST_NOTIFICATIONS权限(Android 13+),并在运行时请求用户授权;
  • 确保已获取后台定位权限(ACCESS_BACKGROUND_LOCATION),仅前台权限无法在后台持续获取定位。

(3) 优化前台服务启动逻辑

在start()方法中,确保startForeground()调用时机合理,避免系统因超时杀死服务;同时可根据需求调整通知渠道的IMPORTANCE(如设为IMPORTANCE_LOW减少对用户的打扰)。

3. 补充必要权限

在Manifest中添加缺失的权限:

<!-- 前台服务权限 -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION" />

<!-- 定位权限 -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />

<!-- Android 13+ 通知权限 -->
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />

注意:ACCESS_BACKGROUND_LOCATION和POST_NOTIFICATIONS需要在运行时动态申请。

4. 测试验证

  • 发送仅含data字段的FCM消息,确认APP后台/被杀状态下onMessageReceived被调用;
  • 检查服务启动后是否显示前台通知,定位数据是否正常写入Firestore;
  • 在Android 12+版本上重点测试权限和服务启动逻辑。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 17:37:12