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
相关产品推荐
相关产品推荐

