Android Kotlin 前台服务在一段时间后停止
Android Kotlin Foreground Service stops after some time
我正在开发用于用户跟踪的前台定位服务。每次位置更新时,此服务都会向 API 发送请求以更新当前位置。问题是当应用程序被置于后台或屏幕被锁定时,服务会在一段时间后停止发送请求(通常大约 1 分钟,在此期间发送大约 10 个请求)。应用程序恢复后,服务再次开始工作,minimizing/locking 屏幕后场景重复。
在 onStartCommand
中,我尝试 return 多个启动选项,但都没有用。我已经在 Android 10 和 11 上测试了该应用程序。
服务源代码:
class LocationService: Service() {
@Inject
lateinit var apiService: ApiService
private val composite = CompositeDisposable()
private var locationManager: LocationManager? = null
private var locationListener: LocationListener? = null
override fun onBind(p0: Intent?): IBinder? =
null
val NOTIFICATION_CHANNEL_ID = "location_tracking"
val NOTIFICATION_CHANNEL_NAME = "Location tracking"
val NOTIFICATION_ID = 101
var isFirstRun = true
@SuppressLint("MissingPermission")
override fun onCreate() {
App.component.inject(this)
setupLocationListener()
locationManager = getSystemService(LOCATION_SERVICE) as LocationManager?
val criteria = Criteria()
criteria.accuracy = Criteria.ACCURACY_FINE
val provider = locationManager?.getBestProvider(criteria, true)
val minTime = 5*1000L
if(provider != null) locationManager?.requestLocationUpdates(provider, minTime, 0f, locationListener)
super.onCreate()
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
if (isFirstRun) {
startForegroundService()
isFirstRun = false
} else {
Timber.d {"Resuming service"}
}
return super.onStartCommand(intent, flags, startId)
}
private fun startForegroundService() {
val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) createNotificationChannel(notificationManager)
val notificationBuilder = NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID)
.setAutoCancel(false)
.setOngoing(true)
.setSmallIcon(R.drawable.ic_notification)
.setContentTitle(NOTIFICATION_CHANNEL_NAME)
.setContentIntent(getMainActivityIntent())
startForeground(NOTIFICATION_ID, notificationBuilder.build())
}
private fun getMainActivityIntent()
= PendingIntent.getActivity(this, 0, Intent(this, MainActivity::class.java)
.also { it.action = R.id.action_global_navigationScreenFragment.toString() }, FLAG_UPDATE_CURRENT)
@RequiresApi(Build.VERSION_CODES.O)
private fun createNotificationChannel(notificationManager: NotificationManager) {
val channel = NotificationChannel(NOTIFICATION_CHANNEL_ID, NOTIFICATION_CHANNEL_NAME, IMPORTANCE_LOW)
notificationManager.createNotificationChannel(channel)
}
private fun setupLocationListener() {
locationListener = object: LocationListener {
override fun onLocationChanged(location: Location) {
val cords = GeoCoordinatesDTO(location.latitude.toFloat(), location.longitude.toFloat())
try {
composite.add(apiService.mobileUserAccountReportPosition(cords)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{},
{ t ->
if(t is RuntimeException) {
e(t)
}
}
))
} catch(e: Exception) {
Log.e("GPS", "error: $e")
}
}
override fun onStatusChanged(provider: String, status: Int, extras: Bundle) {}
override fun onProviderEnabled(provider: String) {}
override fun onProviderDisabled(provider: String) {}
}
}
override fun onDestroy() {
try {
locationManager?.removeUpdates(locationListener)
} catch(e: DeadObjectException) {}
super.onDestroy()
}
}
服务从 MainActivity
中的 onStart
函数启动
private fun initializeLocationMonitor() {
locationService = Intent(this, LocationService::class.java)
if(!this.isServiceRunning(LocationService::class.java)) {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
startForegroundService(locationService)
} else {
startService(locationService)
}
sendBroadcast(locationService)
}
}
我在清单中拥有以下权限并注册了服务:
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<service
android:name=".Services.LocationService"
android:enabled="true"
android:exported="true"
tools:ignore="ExportedService,InnerclassSeparator"
android:foregroundServiceType="location"/>
如果您的最低 sdk 为 14+,最好使用 WorkManager。它将使您免于使用和管理前台服务的许多麻烦。
https://developer.android.com/topic/libraries/architecture/workmanager
发送位置更新是 WorkManager 的最佳用例之一。
// global service variable
private var wakeLock: PowerManager.WakeLock? = null
...
// initialize before setupLocationListener() is called
wakeLock =
(getSystemService(Context.POWER_SERVICE) as PowerManager).run {
newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "LocationService::lock").apply {
acquire(10*1000L) // 10 seconds
}
}
清单:
<uses-permission android:name="android.permission.WAKE_LOCK" />
- 检查您的应用程序设置中是否限制了数据使用和电池服务器。
我正在开发用于用户跟踪的前台定位服务。每次位置更新时,此服务都会向 API 发送请求以更新当前位置。问题是当应用程序被置于后台或屏幕被锁定时,服务会在一段时间后停止发送请求(通常大约 1 分钟,在此期间发送大约 10 个请求)。应用程序恢复后,服务再次开始工作,minimizing/locking 屏幕后场景重复。
在 onStartCommand
中,我尝试 return 多个启动选项,但都没有用。我已经在 Android 10 和 11 上测试了该应用程序。
服务源代码:
class LocationService: Service() {
@Inject
lateinit var apiService: ApiService
private val composite = CompositeDisposable()
private var locationManager: LocationManager? = null
private var locationListener: LocationListener? = null
override fun onBind(p0: Intent?): IBinder? =
null
val NOTIFICATION_CHANNEL_ID = "location_tracking"
val NOTIFICATION_CHANNEL_NAME = "Location tracking"
val NOTIFICATION_ID = 101
var isFirstRun = true
@SuppressLint("MissingPermission")
override fun onCreate() {
App.component.inject(this)
setupLocationListener()
locationManager = getSystemService(LOCATION_SERVICE) as LocationManager?
val criteria = Criteria()
criteria.accuracy = Criteria.ACCURACY_FINE
val provider = locationManager?.getBestProvider(criteria, true)
val minTime = 5*1000L
if(provider != null) locationManager?.requestLocationUpdates(provider, minTime, 0f, locationListener)
super.onCreate()
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
if (isFirstRun) {
startForegroundService()
isFirstRun = false
} else {
Timber.d {"Resuming service"}
}
return super.onStartCommand(intent, flags, startId)
}
private fun startForegroundService() {
val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) createNotificationChannel(notificationManager)
val notificationBuilder = NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID)
.setAutoCancel(false)
.setOngoing(true)
.setSmallIcon(R.drawable.ic_notification)
.setContentTitle(NOTIFICATION_CHANNEL_NAME)
.setContentIntent(getMainActivityIntent())
startForeground(NOTIFICATION_ID, notificationBuilder.build())
}
private fun getMainActivityIntent()
= PendingIntent.getActivity(this, 0, Intent(this, MainActivity::class.java)
.also { it.action = R.id.action_global_navigationScreenFragment.toString() }, FLAG_UPDATE_CURRENT)
@RequiresApi(Build.VERSION_CODES.O)
private fun createNotificationChannel(notificationManager: NotificationManager) {
val channel = NotificationChannel(NOTIFICATION_CHANNEL_ID, NOTIFICATION_CHANNEL_NAME, IMPORTANCE_LOW)
notificationManager.createNotificationChannel(channel)
}
private fun setupLocationListener() {
locationListener = object: LocationListener {
override fun onLocationChanged(location: Location) {
val cords = GeoCoordinatesDTO(location.latitude.toFloat(), location.longitude.toFloat())
try {
composite.add(apiService.mobileUserAccountReportPosition(cords)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{},
{ t ->
if(t is RuntimeException) {
e(t)
}
}
))
} catch(e: Exception) {
Log.e("GPS", "error: $e")
}
}
override fun onStatusChanged(provider: String, status: Int, extras: Bundle) {}
override fun onProviderEnabled(provider: String) {}
override fun onProviderDisabled(provider: String) {}
}
}
override fun onDestroy() {
try {
locationManager?.removeUpdates(locationListener)
} catch(e: DeadObjectException) {}
super.onDestroy()
}
}
服务从 MainActivity
onStart
函数启动
private fun initializeLocationMonitor() {
locationService = Intent(this, LocationService::class.java)
if(!this.isServiceRunning(LocationService::class.java)) {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
startForegroundService(locationService)
} else {
startService(locationService)
}
sendBroadcast(locationService)
}
}
我在清单中拥有以下权限并注册了服务:
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<service
android:name=".Services.LocationService"
android:enabled="true"
android:exported="true"
tools:ignore="ExportedService,InnerclassSeparator"
android:foregroundServiceType="location"/>
如果您的最低 sdk 为 14+,最好使用 WorkManager。它将使您免于使用和管理前台服务的许多麻烦。
https://developer.android.com/topic/libraries/architecture/workmanager
发送位置更新是 WorkManager 的最佳用例之一。
// global service variable
private var wakeLock: PowerManager.WakeLock? = null
...
// initialize before setupLocationListener() is called
wakeLock =
(getSystemService(Context.POWER_SERVICE) as PowerManager).run {
newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "LocationService::lock").apply {
acquire(10*1000L) // 10 seconds
}
}
清单:
<uses-permission android:name="android.permission.WAKE_LOCK" />
- 检查您的应用程序设置中是否限制了数据使用和电池服务器。