Android Phone 旋转时我的前台服务的多个实例
Multiple Instances of my Foreground Service When Android Phone is Rotated
我正在编写 Android 定位应用程序和前台服务。该应用程序会定期从服务获取位置更新。我正在使用 Android Studio 并使用 Kotlin 编写应用程序。
问题是,当 phone 旋转时,会创建一个新的前台服务实例。这通过显示位置更新计数以及服务的哈希码的日志输出进行了演示:
MainActivity.onCreate(Buddle)
MainActivity.onStart
MainActivity.onResume
>>>>>>>>>>>>>>>>>>> LocationService: onStartCommand
MainActivity: ======= Location Changed =======
LocationService: ******* onLocation Change ******* - Count: 1 Service Hash Code: 185408788 <<<<<<<<<<<<<<
MainActivity: ======= Location Changed =======
LocationService: ******* onLocation Change ******* - Count: 2 Service Hash Code: 185408788 <<<<<<<<<<<<<<
MainActivity: ======= Location Changed =======
LocationService: ******* onLocation Change ******* - Count: 3 Service Hash Code: 185408788 <<<<<<<<<<<<<<
MainActivity: ======= Location Changed =======
LocationService: ******* onLocation Change ******* - Count: 4 Service Hash Code: 185408788 <<<<<<<<<<<<<<
MainActivity: ======= Location Changed =======
LocationService: ******* onLocation Change ******* - Count: 5 Service Hash Code: 185408788 <<<<<<<<<<<<<<
MainActivity: ======= Location Changed =======
LocationService: ******* onLocation Change ******* - Count: 6 Service Hash Code: 185408788 <<<<<<<<<<<<<<
MainActivity: ======= Location Changed =======
LocationService: ******* onLocation Change ******* - Count: 7 Service Hash Code: 185408788 <<<<<<<<<<<<<<
MainActivity: ======= Location Changed =======
MainActivity.onPause
MainActivity.onStop
MainActivity.onSaveInstanceState
MainActivity.onCreate(Buddle)
MainActivity.onStart
MainActivity: ======= Location Changed =======
MainActivity.onResume
LocationService: onDestroy ========================================================
>>>>>>>>>>>>>>>>>>> LocationService: onStartCommand
MainActivity: ======= Location Changed =======
LocationService: ******* onLocation Change ******* - Count: 8 Service Hash Code: 185408788 <<<<<<<<<<<<<<
MainActivity: ======= Location Changed =======
LocationService: ******* onLocation Change ******* - Count: 1 Service Hash Code: 233645514 <<<<<<<<<<<<<<
MainActivity: ======= Location Changed =======
LocationService: ******* onLocation Change ******* - Count: 9 Service Hash Code: 185408788 <<<<<<<<<<<<<<
MainActivity: ======= Location Changed =======
LocationService: ******* onLocation Change ******* - Count: 2 Service Hash Code: 233645514 <<<<<<<<<<<<<<
MainActivity: ======= Location Changed =======
LocationService: ******* onLocation Change ******* - Count: 10 Service Hash Code: 185408788 <<<<<<<<<<<<<<
MainActivity: ======= Location Changed =======
LocationService: ******* onLocation Change ******* - Count: 3 Service Hash Code: 233645514 <<<<<<<<<<<<<<
MainActivity: ======= Location Changed =======
LocationService: ******* onLocation Change ******* - Count: 11 Service Hash Code: 185408788 <<<<<<<<<<<<<<
MainActivity: ======= Location Changed =======
LocationService: ******* onLocation Change ******* - Count: 4 Service Hash Code: 233645514 <<<<<<<<<<<<<<
MainActivity: ======= Location Changed =======
LocationService: ******* onLocation Change ******* - Count: 12 Service Hash Code: 185408788 <<<<<<<<<<<<<<
MainActivity: ======= Location Changed =======
LocationService: ******* onLocation Change ******* - Count: 5 Service Hash Code: 233645514 <<<<<<<<<<<<<<
MainActivity: ======= Location Changed =======
phone 旋转发生在生命周期 activity 发生的中途。过去两天我一直在研究这个问题并在网上搜索帮助,但我看到的关于这个问题的唯一答案是,“你不能创建一个服务的多个实例”但是日志 似乎 否则证明。我一直不愿意在 stackoverflow 上提问,因为我是新手,不完全知道提问的正确方法,但我不知道还能尝试什么. 如果我的问题中有什么地方忘记了,请告诉我,谢谢。
主要清单:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.considerateapps.logger">
<!-- To request foreground location access, declare one of these permissions. -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
<application
android:allowBackup="true"
android:fullBackupContent="true"
android:hasFragileUserData="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.Logger">
<service
android:name=".ui.main.LocationService"
android:foregroundServiceType="location"
android:exported="false"
android:stopWithTask="true"
/>
<meta-data
android:name="com.google.android.geo.API_KEY"
android:value="AIzaSyBybVSRyyvJbnHUfYRRi3PJEsodusgKX78" />
<activity
android:name=".MainActivity"
android:label="Logger"
android:theme="@style/Theme.Logger.NoActionBar">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
主要活动:
package com.considerateapps.logger
import android.Manifest
import android.content.pm.PackageManager
import android.media.MediaPlayer
import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.observe
import com.considerateapps.logger.ui.main.*
class MainActivity : AppCompatActivity()
{
public val TAG = "Logger"
private val locationPermissionCode = 2
override fun onCreate(savedInstanceState: Bundle?)
{
Log.i(TAG, "MainActivity.onCreate(Buddle)")
checkLocationPermission()
startForegroundService()
super.onCreate(savedInstanceState)
setContentView(R.layout.main_activity)
setSupportActionBar(findViewById(R.id.toolbar))
setupObservers()
}
private fun setupObservers()
{
LocationService.location.observe(this as LifecycleOwner)
{
Log.i(TAG, "MainFragment: ======= database Updated Observer ======= ")
val mediaPlayerButtonSound: MediaPlayer = MediaPlayer.create(this, R.raw.ding)
mediaPlayerButtonSound.start()
}
}
private fun checkLocationPermission()
{
if ((ContextCompat.checkSelfPermission(
application,
Manifest.permission.ACCESS_FINE_LOCATION
) != PackageManager.PERMISSION_GRANTED)
) {
ActivityCompat.requestPermissions(
this,
arrayOf(Manifest.permission.ACCESS_FINE_LOCATION),
locationPermissionCode
)
}
}
private fun startForegroundService()
{
LocationService.startService(this, "Foreground Service is running...")
}
override fun onStart()
{
Log.i(TAG, "MainActivity.onStart")
super.onStart()
}
override fun onResume()
{
Log.i(TAG, "MainActivity.onResume")
super.onResume()
}
override fun onPause()
{
Log.i(TAG, "MainActivity.onPause")
super.onPause()
}
override fun onStop()
{
Log.i(TAG, "MainActivity.onStop")
super.onStop()
}
override fun onSaveInstanceState(outState: Bundle)
{
Log.i(TAG, "MainActivity.onSaveInstanceState")
super.onSaveInstanceState(outState)
}
override fun onLowMemory()
{
Log.i(TAG, "$localClassName.onLowMemory")
super.onLowMemory()
}
override fun onDestroy()
{
LocationService.stopService(this)
super.onDestroy()
}
}
LocationService.kt:
package com.considerateapps.logger.ui.main
import android.Manifest
import android.app.*
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.location.Location
import android.location.LocationListener
import android.location.LocationManager
import android.os.IBinder
import android.util.Log
import androidx.core.app.NotificationCompat
import androidx.core.content.ContextCompat
import androidx.lifecycle.MutableLiveData
import com.considerateapps.logger.MainActivity
import com.considerateapps.logger.R
class LocationService : Service(),
LocationListener
{
val TAG = "Logger"
val NOTIF_ID = 1
private lateinit var locationManager: LocationManager
private var locationUpdateCount = 0
private val CHANNEL_ID = "ForegroundService Kotlin"
companion object
{
val location:MutableLiveData<Location> = MutableLiveData<Location>()
fun startService(context: Context, message: String)
{
val startIntent = Intent(context, LocationService::class.java)
startIntent.putExtra("inputExtra", message)
ContextCompat.startForegroundService(context, startIntent)
}
fun stopService(context: Context)
{
val stopIntent = Intent(context, LocationService::class.java)
context.stopService(stopIntent)
}
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int
{
Log.i(TAG, ">>>>>>>>>>>>>>>>>>> LocationService: onStartCommand")
locationManager = application.getSystemService(Context.LOCATION_SERVICE) as LocationManager
//do heavy work on a background thread
val input = intent?.getStringExtra("inputExtra")
createNotificationChannel()
val notification = getNotification(input!!)
startForeground(NOTIF_ID, notification)
requestLocationUpdates()
super.onStartCommand(intent, flags, startId)
return START_STICKY
}
private fun getNotification(contentText: String):Notification
{
val notificationIntent = Intent(this, MainActivity::class.java)
notificationIntent.flags = Intent.FLAG_ACTIVITY_SINGLE_TOP
val pendingIntent = PendingIntent.getActivity(
this,
0, notificationIntent,
PendingIntent.FLAG_UPDATE_CURRENT
)
val notification = NotificationCompat.Builder(this, CHANNEL_ID)
.setOngoing(false)
.setAutoCancel(false)
.setContentTitle("Location Service")
.setContentText(contentText)
.setSmallIcon(R.drawable.ic_launcher_foreground)
.setContentIntent(pendingIntent)
.setOnlyAlertOnce(true)
.build()
return notification
}
private fun updateNotification(contentText: String)
{
val notification: Notification = getNotification(contentText)
val mNotificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
mNotificationManager.notify(NOTIF_ID, notification)
}
override fun onBind(intent: Intent): IBinder?
{
return null
}
private fun createNotificationChannel() {
val serviceChannel = NotificationChannel(CHANNEL_ID, "Foreground Service Channel",
NotificationManager.IMPORTANCE_DEFAULT)
val manager = getSystemService(NotificationManager::class.java)
manager!!.createNotificationChannel(serviceChannel)
}
private fun requestLocationUpdates()
{
if((ContextCompat.checkSelfPermission(application, Manifest.permission.ACCESS_FINE_LOCATION)) ==PackageManager.PERMISSION_GRANTED)
{
location.value = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER)
locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 200, 0f, this)
}
}
override fun onLocationChanged(i_location: Location) {
locationUpdateCount++
Log.i(TAG, "LocationService: ******* onLocation Change ******* - Count: $locationUpdateCount Service Hash Code: ${hashCode()} <<<<<<<<<<<<<<")
// Toast.makeText(this, "Count:$locationUpdateCount", Toast.LENGTH_SHORT).show();
updateNotification("Count: $locationUpdateCount")
location.value = i_location
}
override fun onDestroy()
{
Log.i(TAG, "LocationService: onDestroy ========================================================")
super.onDestroy()
}
}
经过几个小时的调试我发现了问题:
在 MainActivity.onDestroy() 中,我试图停止我的 Service LocationService。问题是我将位置回调附加到服务,因此当 phone 旋转时服务泄漏,因为调用 MainActivity.onDestroy() 试图停止服务但显然不能因为附加的 Location Listener 和 Android 似乎假定它已成功停止该服务,因此在下次创建它的新实例。
*** 似乎是一个 Android OS 错误 ***
我正在编写 Android 定位应用程序和前台服务。该应用程序会定期从服务获取位置更新。我正在使用 Android Studio 并使用 Kotlin 编写应用程序。
问题是,当 phone 旋转时,会创建一个新的前台服务实例。这通过显示位置更新计数以及服务的哈希码的日志输出进行了演示:
MainActivity.onCreate(Buddle)
MainActivity.onStart
MainActivity.onResume
>>>>>>>>>>>>>>>>>>> LocationService: onStartCommand
MainActivity: ======= Location Changed =======
LocationService: ******* onLocation Change ******* - Count: 1 Service Hash Code: 185408788 <<<<<<<<<<<<<<
MainActivity: ======= Location Changed =======
LocationService: ******* onLocation Change ******* - Count: 2 Service Hash Code: 185408788 <<<<<<<<<<<<<<
MainActivity: ======= Location Changed =======
LocationService: ******* onLocation Change ******* - Count: 3 Service Hash Code: 185408788 <<<<<<<<<<<<<<
MainActivity: ======= Location Changed =======
LocationService: ******* onLocation Change ******* - Count: 4 Service Hash Code: 185408788 <<<<<<<<<<<<<<
MainActivity: ======= Location Changed =======
LocationService: ******* onLocation Change ******* - Count: 5 Service Hash Code: 185408788 <<<<<<<<<<<<<<
MainActivity: ======= Location Changed =======
LocationService: ******* onLocation Change ******* - Count: 6 Service Hash Code: 185408788 <<<<<<<<<<<<<<
MainActivity: ======= Location Changed =======
LocationService: ******* onLocation Change ******* - Count: 7 Service Hash Code: 185408788 <<<<<<<<<<<<<<
MainActivity: ======= Location Changed =======
MainActivity.onPause
MainActivity.onStop
MainActivity.onSaveInstanceState
MainActivity.onCreate(Buddle)
MainActivity.onStart
MainActivity: ======= Location Changed =======
MainActivity.onResume
LocationService: onDestroy ========================================================
>>>>>>>>>>>>>>>>>>> LocationService: onStartCommand
MainActivity: ======= Location Changed =======
LocationService: ******* onLocation Change ******* - Count: 8 Service Hash Code: 185408788 <<<<<<<<<<<<<<
MainActivity: ======= Location Changed =======
LocationService: ******* onLocation Change ******* - Count: 1 Service Hash Code: 233645514 <<<<<<<<<<<<<<
MainActivity: ======= Location Changed =======
LocationService: ******* onLocation Change ******* - Count: 9 Service Hash Code: 185408788 <<<<<<<<<<<<<<
MainActivity: ======= Location Changed =======
LocationService: ******* onLocation Change ******* - Count: 2 Service Hash Code: 233645514 <<<<<<<<<<<<<<
MainActivity: ======= Location Changed =======
LocationService: ******* onLocation Change ******* - Count: 10 Service Hash Code: 185408788 <<<<<<<<<<<<<<
MainActivity: ======= Location Changed =======
LocationService: ******* onLocation Change ******* - Count: 3 Service Hash Code: 233645514 <<<<<<<<<<<<<<
MainActivity: ======= Location Changed =======
LocationService: ******* onLocation Change ******* - Count: 11 Service Hash Code: 185408788 <<<<<<<<<<<<<<
MainActivity: ======= Location Changed =======
LocationService: ******* onLocation Change ******* - Count: 4 Service Hash Code: 233645514 <<<<<<<<<<<<<<
MainActivity: ======= Location Changed =======
LocationService: ******* onLocation Change ******* - Count: 12 Service Hash Code: 185408788 <<<<<<<<<<<<<<
MainActivity: ======= Location Changed =======
LocationService: ******* onLocation Change ******* - Count: 5 Service Hash Code: 233645514 <<<<<<<<<<<<<<
MainActivity: ======= Location Changed =======
phone 旋转发生在生命周期 activity 发生的中途。过去两天我一直在研究这个问题并在网上搜索帮助,但我看到的关于这个问题的唯一答案是,“你不能创建一个服务的多个实例”但是日志 似乎 否则证明。我一直不愿意在 stackoverflow 上提问,因为我是新手,不完全知道提问的正确方法,但我不知道还能尝试什么. 如果我的问题中有什么地方忘记了,请告诉我,谢谢。
主要清单:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.considerateapps.logger">
<!-- To request foreground location access, declare one of these permissions. -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
<application
android:allowBackup="true"
android:fullBackupContent="true"
android:hasFragileUserData="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.Logger">
<service
android:name=".ui.main.LocationService"
android:foregroundServiceType="location"
android:exported="false"
android:stopWithTask="true"
/>
<meta-data
android:name="com.google.android.geo.API_KEY"
android:value="AIzaSyBybVSRyyvJbnHUfYRRi3PJEsodusgKX78" />
<activity
android:name=".MainActivity"
android:label="Logger"
android:theme="@style/Theme.Logger.NoActionBar">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
主要活动:
package com.considerateapps.logger
import android.Manifest
import android.content.pm.PackageManager
import android.media.MediaPlayer
import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.observe
import com.considerateapps.logger.ui.main.*
class MainActivity : AppCompatActivity()
{
public val TAG = "Logger"
private val locationPermissionCode = 2
override fun onCreate(savedInstanceState: Bundle?)
{
Log.i(TAG, "MainActivity.onCreate(Buddle)")
checkLocationPermission()
startForegroundService()
super.onCreate(savedInstanceState)
setContentView(R.layout.main_activity)
setSupportActionBar(findViewById(R.id.toolbar))
setupObservers()
}
private fun setupObservers()
{
LocationService.location.observe(this as LifecycleOwner)
{
Log.i(TAG, "MainFragment: ======= database Updated Observer ======= ")
val mediaPlayerButtonSound: MediaPlayer = MediaPlayer.create(this, R.raw.ding)
mediaPlayerButtonSound.start()
}
}
private fun checkLocationPermission()
{
if ((ContextCompat.checkSelfPermission(
application,
Manifest.permission.ACCESS_FINE_LOCATION
) != PackageManager.PERMISSION_GRANTED)
) {
ActivityCompat.requestPermissions(
this,
arrayOf(Manifest.permission.ACCESS_FINE_LOCATION),
locationPermissionCode
)
}
}
private fun startForegroundService()
{
LocationService.startService(this, "Foreground Service is running...")
}
override fun onStart()
{
Log.i(TAG, "MainActivity.onStart")
super.onStart()
}
override fun onResume()
{
Log.i(TAG, "MainActivity.onResume")
super.onResume()
}
override fun onPause()
{
Log.i(TAG, "MainActivity.onPause")
super.onPause()
}
override fun onStop()
{
Log.i(TAG, "MainActivity.onStop")
super.onStop()
}
override fun onSaveInstanceState(outState: Bundle)
{
Log.i(TAG, "MainActivity.onSaveInstanceState")
super.onSaveInstanceState(outState)
}
override fun onLowMemory()
{
Log.i(TAG, "$localClassName.onLowMemory")
super.onLowMemory()
}
override fun onDestroy()
{
LocationService.stopService(this)
super.onDestroy()
}
}
LocationService.kt:
package com.considerateapps.logger.ui.main
import android.Manifest
import android.app.*
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.location.Location
import android.location.LocationListener
import android.location.LocationManager
import android.os.IBinder
import android.util.Log
import androidx.core.app.NotificationCompat
import androidx.core.content.ContextCompat
import androidx.lifecycle.MutableLiveData
import com.considerateapps.logger.MainActivity
import com.considerateapps.logger.R
class LocationService : Service(),
LocationListener
{
val TAG = "Logger"
val NOTIF_ID = 1
private lateinit var locationManager: LocationManager
private var locationUpdateCount = 0
private val CHANNEL_ID = "ForegroundService Kotlin"
companion object
{
val location:MutableLiveData<Location> = MutableLiveData<Location>()
fun startService(context: Context, message: String)
{
val startIntent = Intent(context, LocationService::class.java)
startIntent.putExtra("inputExtra", message)
ContextCompat.startForegroundService(context, startIntent)
}
fun stopService(context: Context)
{
val stopIntent = Intent(context, LocationService::class.java)
context.stopService(stopIntent)
}
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int
{
Log.i(TAG, ">>>>>>>>>>>>>>>>>>> LocationService: onStartCommand")
locationManager = application.getSystemService(Context.LOCATION_SERVICE) as LocationManager
//do heavy work on a background thread
val input = intent?.getStringExtra("inputExtra")
createNotificationChannel()
val notification = getNotification(input!!)
startForeground(NOTIF_ID, notification)
requestLocationUpdates()
super.onStartCommand(intent, flags, startId)
return START_STICKY
}
private fun getNotification(contentText: String):Notification
{
val notificationIntent = Intent(this, MainActivity::class.java)
notificationIntent.flags = Intent.FLAG_ACTIVITY_SINGLE_TOP
val pendingIntent = PendingIntent.getActivity(
this,
0, notificationIntent,
PendingIntent.FLAG_UPDATE_CURRENT
)
val notification = NotificationCompat.Builder(this, CHANNEL_ID)
.setOngoing(false)
.setAutoCancel(false)
.setContentTitle("Location Service")
.setContentText(contentText)
.setSmallIcon(R.drawable.ic_launcher_foreground)
.setContentIntent(pendingIntent)
.setOnlyAlertOnce(true)
.build()
return notification
}
private fun updateNotification(contentText: String)
{
val notification: Notification = getNotification(contentText)
val mNotificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
mNotificationManager.notify(NOTIF_ID, notification)
}
override fun onBind(intent: Intent): IBinder?
{
return null
}
private fun createNotificationChannel() {
val serviceChannel = NotificationChannel(CHANNEL_ID, "Foreground Service Channel",
NotificationManager.IMPORTANCE_DEFAULT)
val manager = getSystemService(NotificationManager::class.java)
manager!!.createNotificationChannel(serviceChannel)
}
private fun requestLocationUpdates()
{
if((ContextCompat.checkSelfPermission(application, Manifest.permission.ACCESS_FINE_LOCATION)) ==PackageManager.PERMISSION_GRANTED)
{
location.value = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER)
locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 200, 0f, this)
}
}
override fun onLocationChanged(i_location: Location) {
locationUpdateCount++
Log.i(TAG, "LocationService: ******* onLocation Change ******* - Count: $locationUpdateCount Service Hash Code: ${hashCode()} <<<<<<<<<<<<<<")
// Toast.makeText(this, "Count:$locationUpdateCount", Toast.LENGTH_SHORT).show();
updateNotification("Count: $locationUpdateCount")
location.value = i_location
}
override fun onDestroy()
{
Log.i(TAG, "LocationService: onDestroy ========================================================")
super.onDestroy()
}
}
经过几个小时的调试我发现了问题:
在 MainActivity.onDestroy() 中,我试图停止我的 Service LocationService。问题是我将位置回调附加到服务,因此当 phone 旋转时服务泄漏,因为调用 MainActivity.onDestroy() 试图停止服务但显然不能因为附加的 Location Listener 和 Android 似乎假定它已成功停止该服务,因此在下次创建它的新实例。
*** 似乎是一个 Android OS 错误 ***