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 错误 ***