前台服务被杀死
Foreground service getting killed
我有一项服务,会不时更新我的后端,使用用户的最新位置,问题是,一些用户报告前台通知有时会消失,但并非所有人都这样用户,可能有用户每天使用该应用程序 8 小时,它不会消失,但 Crashlytics 不会出现崩溃。
这是我的服务类
package com.xxxxxxx.xxxxxxxx.service
import android.annotation.SuppressLint
import android.app.Notification
import android.app.Notification.FLAG_NO_CLEAR
import android.app.Notification.FLAG_ONGOING_EVENT
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.app.PendingIntent.FLAG_UPDATE_CURRENT
import android.app.Service
import android.content.Intent
import android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP
import android.content.Intent.FLAG_ACTIVITY_SINGLE_TOP
import android.graphics.Color
import android.location.Location
import android.os.Build.VERSION.SDK_INT
import android.os.Build.VERSION_CODES.O
import android.os.IBinder
import android.util.Base64
import android.util.Base64.DEFAULT
import androidx.annotation.RequiresApi
import androidx.core.app.NotificationCompat
import androidx.core.content.ContextCompat
import com.google.android.gms.location.LocationCallback
import com.google.android.gms.location.LocationRequest
import com.google.android.gms.location.LocationResult
import com.google.android.gms.location.LocationServices
import com.google.android.gms.maps.model.LatLng
import com.xxxx.commons.XXXXXXCommons
import com.xxxxxxx.xxxxxxxx.R
import com.xxxxxxx.xxxxxxxx.di.DataSourceModule
import com.xxxxxxx.xxxxxxxx.domain.GetAppStateUseCase.Companion.LOCATION_DELIMITER
import com.xxxxxxx.xxxxxxxx.model.local.CachedLocation
import com.xxxxxxx.xxxxxxxx.model.local.DriverLocation
import com.xxxxxxx.xxxxxxxx.utils.LOCATION_PERMISSIONS
import com.xxxxxxx.xxxxxxxx.utils.OnGoingTrip
import com.xxxxxxx.xxxxxxxx.utils.checkPermissions
import com.xxxxxxx.xxxxxxxx.view.activity.SplashActivity
import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.cancel
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import kotlin.text.Charsets.UTF_8
class LocationTrackingService : Service() {
private companion object {
const val NOTIFICATION_ID = 856
const val FOREGROUND_SERVICE_ID = 529
const val LOCATION_UPDATE_INTERVAL = 10000L
const val LOCATION_UPDATE_FASTEST_INTERVAL = 5000L
}
private val updateJob: Job = Job()
private val errorHandler = CoroutineExceptionHandler { _, throwable ->
XXXXXXCommons.crashHandler?.log(throwable)
}
private val locationTrackingCoroutineScope =
CoroutineScope(Dispatchers.IO + updateJob + errorHandler)
private val rideApiService by lazy { DataSourceModule.provideRideApiService() }
private val locationDao by lazy { DataSourceModule.provideLocationDao(XXXXXXCommons.provideApplicationContext()) }
private val locationRequest = LocationRequest().apply {
interval = LOCATION_UPDATE_INTERVAL
fastestInterval = LOCATION_UPDATE_FASTEST_INTERVAL
priority = LocationRequest.PRIORITY_HIGH_ACCURACY
}
private val callback = object : LocationCallback() {
override fun onLocationResult(result: LocationResult?) = onLocationReceived(result)
}
@SuppressLint("MissingPermission")
private fun requestLocationUpdates() {
if (checkPermissions(*LOCATION_PERMISSIONS)) {
LocationServices.getFusedLocationProviderClient(this)
.requestLocationUpdates(locationRequest, callback, null)
}
}
private fun onLocationReceived(result: LocationResult?) = result?.lastLocation?.run {
postDriverLocation(this)
} ?: Unit
private fun postDriverLocation(location: Location) {
val driverLocation = DriverLocation(
location.latitude.toFloat(),
location.longitude.toFloat(),
location.bearing,
OnGoingTrip.rideData.rideId,
OnGoingTrip.orderData.orderId
)
OnGoingTrip.currentLocation = LatLng(location.latitude, location.longitude)
OnGoingTrip.reportLocationUpdate()
locationTrackingCoroutineScope.launch(Dispatchers.IO) {
cacheDriverLocation(driverLocation)
rideApiService.postCurrentDriverLocation(driverLocation)
}
}
private suspend fun cacheDriverLocation(driverLocation: DriverLocation) {
val bytes = "${driverLocation.latitude}$LOCATION_DELIMITER${driverLocation.longitude}"
.toByteArray(UTF_8)
val safeLocation = Base64.encode(bytes, DEFAULT).toString(UTF_8)
locationDao.createOrUpdate(CachedLocation(location = safeLocation))
}
override fun onBind(intent: Intent): IBinder? = null
override fun onCreate() {
super.onCreate()
buildNotification(
if (SDK_INT >= O) {
createNotificationChannel(getString(R.string.app_name))
} else {
NOTIFICATION_ID.toString()
}
)
requestLocationUpdates()
}
override fun onDestroy() {
super.onDestroy()
if (locationTrackingCoroutineScope.isActive) {
locationTrackingCoroutineScope.cancel()
}
LocationServices.getFusedLocationProviderClient(this).removeLocationUpdates(callback)
(getSystemService(NOTIFICATION_SERVICE) as? NotificationManager)?.cancel(NOTIFICATION_ID)
}
@RequiresApi(O)
private fun createNotificationChannel(channelName: String): String {
val chan = NotificationChannel(
NOTIFICATION_ID.toString(),
channelName,
NotificationManager.IMPORTANCE_NONE
)
chan.lightColor = Color.CYAN
chan.lockscreenVisibility = Notification.VISIBILITY_PRIVATE
val service = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
service.createNotificationChannel(chan)
return NOTIFICATION_ID.toString()
}
private fun buildNotification(channelId: String) {
val ctx = this
(getSystemService(NOTIFICATION_SERVICE) as? NotificationManager)?.run {
val intent = Intent(ctx, SplashActivity::class.java).apply {
flags = FLAG_ACTIVITY_CLEAR_TOP or FLAG_ACTIVITY_SINGLE_TOP
}
val resultPendingIntent =
PendingIntent.getActivity(ctx, 0, intent, FLAG_UPDATE_CURRENT)
val notification = NotificationCompat.Builder(ctx, channelId)
.setSmallIcon(R.drawable.ic_xxxxxx_icon_background)
.setColor(ContextCompat.getColor(ctx, R.color.xxxxxxTurquoise))
.setContentTitle(ctx.getString(R.string.app_name))
.setOngoing(true)
.setContentText(ctx.getString(R.string.usage_message))
.setContentIntent(resultPendingIntent)
.build().apply { flags = FLAG_ONGOING_EVENT or FLAG_NO_CLEAR }
startForeground(FOREGROUND_SERVICE_ID, notification)
}
}
}
以下是我启动服务的方式:
startService(Intent(this, LocationTrackingService::class.java));
这是我拥有的权限以及我在 AndroidManifest.xml 上声明它的方式:
<service
android:name=".service.LocationTrackingService"
android:enabled="true"
android:exported="true" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<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" />
编辑
大部分设备运行Android9和10,少数5到8,几乎没有11
我很乐意分享更多信息。
这里有几件事。我在你的清单中看不到 <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
。还要确保清单内服务属性中的 foregroundServiceType="location"
。此外,如果您有定位的前台服务,则不需要 ACCESS_BACKGROUND_LOCATION
。
另一方面,当用户从应用程序切换器关闭应用程序时,小米设备会终止前台服务(小米是最糟糕的),您应该要求小米用户手动排除您的应用程序在节电模式或应用程序详细信息中的额外权限中被终止(和即使他们这样做了,也不能保证,而且不同的 MiUI 版本有不同的规则)
此外,使用 ContextCompat.startForegroundService
并在服务的 onStartCommand 调用中为您的服务加注星标 startForeground
Android11 有几个最新的限制。很难提供准确的解决方案。以下是您应该重新检查的几件事,它们可能会解决您的问题:
- 在设备中为您的应用停用电池优化。
- 检查设备是否未在休眠模式下运行。
- 在清单中指定 android:foregroundServiceType。
我有一项服务,会不时更新我的后端,使用用户的最新位置,问题是,一些用户报告前台通知有时会消失,但并非所有人都这样用户,可能有用户每天使用该应用程序 8 小时,它不会消失,但 Crashlytics 不会出现崩溃。
这是我的服务类
package com.xxxxxxx.xxxxxxxx.service
import android.annotation.SuppressLint
import android.app.Notification
import android.app.Notification.FLAG_NO_CLEAR
import android.app.Notification.FLAG_ONGOING_EVENT
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.app.PendingIntent.FLAG_UPDATE_CURRENT
import android.app.Service
import android.content.Intent
import android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP
import android.content.Intent.FLAG_ACTIVITY_SINGLE_TOP
import android.graphics.Color
import android.location.Location
import android.os.Build.VERSION.SDK_INT
import android.os.Build.VERSION_CODES.O
import android.os.IBinder
import android.util.Base64
import android.util.Base64.DEFAULT
import androidx.annotation.RequiresApi
import androidx.core.app.NotificationCompat
import androidx.core.content.ContextCompat
import com.google.android.gms.location.LocationCallback
import com.google.android.gms.location.LocationRequest
import com.google.android.gms.location.LocationResult
import com.google.android.gms.location.LocationServices
import com.google.android.gms.maps.model.LatLng
import com.xxxx.commons.XXXXXXCommons
import com.xxxxxxx.xxxxxxxx.R
import com.xxxxxxx.xxxxxxxx.di.DataSourceModule
import com.xxxxxxx.xxxxxxxx.domain.GetAppStateUseCase.Companion.LOCATION_DELIMITER
import com.xxxxxxx.xxxxxxxx.model.local.CachedLocation
import com.xxxxxxx.xxxxxxxx.model.local.DriverLocation
import com.xxxxxxx.xxxxxxxx.utils.LOCATION_PERMISSIONS
import com.xxxxxxx.xxxxxxxx.utils.OnGoingTrip
import com.xxxxxxx.xxxxxxxx.utils.checkPermissions
import com.xxxxxxx.xxxxxxxx.view.activity.SplashActivity
import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.cancel
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import kotlin.text.Charsets.UTF_8
class LocationTrackingService : Service() {
private companion object {
const val NOTIFICATION_ID = 856
const val FOREGROUND_SERVICE_ID = 529
const val LOCATION_UPDATE_INTERVAL = 10000L
const val LOCATION_UPDATE_FASTEST_INTERVAL = 5000L
}
private val updateJob: Job = Job()
private val errorHandler = CoroutineExceptionHandler { _, throwable ->
XXXXXXCommons.crashHandler?.log(throwable)
}
private val locationTrackingCoroutineScope =
CoroutineScope(Dispatchers.IO + updateJob + errorHandler)
private val rideApiService by lazy { DataSourceModule.provideRideApiService() }
private val locationDao by lazy { DataSourceModule.provideLocationDao(XXXXXXCommons.provideApplicationContext()) }
private val locationRequest = LocationRequest().apply {
interval = LOCATION_UPDATE_INTERVAL
fastestInterval = LOCATION_UPDATE_FASTEST_INTERVAL
priority = LocationRequest.PRIORITY_HIGH_ACCURACY
}
private val callback = object : LocationCallback() {
override fun onLocationResult(result: LocationResult?) = onLocationReceived(result)
}
@SuppressLint("MissingPermission")
private fun requestLocationUpdates() {
if (checkPermissions(*LOCATION_PERMISSIONS)) {
LocationServices.getFusedLocationProviderClient(this)
.requestLocationUpdates(locationRequest, callback, null)
}
}
private fun onLocationReceived(result: LocationResult?) = result?.lastLocation?.run {
postDriverLocation(this)
} ?: Unit
private fun postDriverLocation(location: Location) {
val driverLocation = DriverLocation(
location.latitude.toFloat(),
location.longitude.toFloat(),
location.bearing,
OnGoingTrip.rideData.rideId,
OnGoingTrip.orderData.orderId
)
OnGoingTrip.currentLocation = LatLng(location.latitude, location.longitude)
OnGoingTrip.reportLocationUpdate()
locationTrackingCoroutineScope.launch(Dispatchers.IO) {
cacheDriverLocation(driverLocation)
rideApiService.postCurrentDriverLocation(driverLocation)
}
}
private suspend fun cacheDriverLocation(driverLocation: DriverLocation) {
val bytes = "${driverLocation.latitude}$LOCATION_DELIMITER${driverLocation.longitude}"
.toByteArray(UTF_8)
val safeLocation = Base64.encode(bytes, DEFAULT).toString(UTF_8)
locationDao.createOrUpdate(CachedLocation(location = safeLocation))
}
override fun onBind(intent: Intent): IBinder? = null
override fun onCreate() {
super.onCreate()
buildNotification(
if (SDK_INT >= O) {
createNotificationChannel(getString(R.string.app_name))
} else {
NOTIFICATION_ID.toString()
}
)
requestLocationUpdates()
}
override fun onDestroy() {
super.onDestroy()
if (locationTrackingCoroutineScope.isActive) {
locationTrackingCoroutineScope.cancel()
}
LocationServices.getFusedLocationProviderClient(this).removeLocationUpdates(callback)
(getSystemService(NOTIFICATION_SERVICE) as? NotificationManager)?.cancel(NOTIFICATION_ID)
}
@RequiresApi(O)
private fun createNotificationChannel(channelName: String): String {
val chan = NotificationChannel(
NOTIFICATION_ID.toString(),
channelName,
NotificationManager.IMPORTANCE_NONE
)
chan.lightColor = Color.CYAN
chan.lockscreenVisibility = Notification.VISIBILITY_PRIVATE
val service = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
service.createNotificationChannel(chan)
return NOTIFICATION_ID.toString()
}
private fun buildNotification(channelId: String) {
val ctx = this
(getSystemService(NOTIFICATION_SERVICE) as? NotificationManager)?.run {
val intent = Intent(ctx, SplashActivity::class.java).apply {
flags = FLAG_ACTIVITY_CLEAR_TOP or FLAG_ACTIVITY_SINGLE_TOP
}
val resultPendingIntent =
PendingIntent.getActivity(ctx, 0, intent, FLAG_UPDATE_CURRENT)
val notification = NotificationCompat.Builder(ctx, channelId)
.setSmallIcon(R.drawable.ic_xxxxxx_icon_background)
.setColor(ContextCompat.getColor(ctx, R.color.xxxxxxTurquoise))
.setContentTitle(ctx.getString(R.string.app_name))
.setOngoing(true)
.setContentText(ctx.getString(R.string.usage_message))
.setContentIntent(resultPendingIntent)
.build().apply { flags = FLAG_ONGOING_EVENT or FLAG_NO_CLEAR }
startForeground(FOREGROUND_SERVICE_ID, notification)
}
}
}
以下是我启动服务的方式:
startService(Intent(this, LocationTrackingService::class.java));
这是我拥有的权限以及我在 AndroidManifest.xml 上声明它的方式:
<service
android:name=".service.LocationTrackingService"
android:enabled="true"
android:exported="true" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<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" />
编辑
大部分设备运行Android9和10,少数5到8,几乎没有11
我很乐意分享更多信息。
这里有几件事。我在你的清单中看不到 <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
。还要确保清单内服务属性中的 foregroundServiceType="location"
。此外,如果您有定位的前台服务,则不需要 ACCESS_BACKGROUND_LOCATION
。
另一方面,当用户从应用程序切换器关闭应用程序时,小米设备会终止前台服务(小米是最糟糕的),您应该要求小米用户手动排除您的应用程序在节电模式或应用程序详细信息中的额外权限中被终止(和即使他们这样做了,也不能保证,而且不同的 MiUI 版本有不同的规则)
此外,使用 ContextCompat.startForegroundService
并在服务的 onStartCommand 调用中为您的服务加注星标 startForeground
Android11 有几个最新的限制。很难提供准确的解决方案。以下是您应该重新检查的几件事,它们可能会解决您的问题:
- 在设备中为您的应用停用电池优化。
- 检查设备是否未在休眠模式下运行。
- 在清单中指定 android:foregroundServiceType。