I'm writing an Android location app and foreground service. The app gets location updates from the service periodically. I'm using Android Studio and writing the app in Kotlin.
The problem is that when the phone is rotated a new instance of the foreground service is created. This is demonstrated with Log outputs that show the count of location updates as well as the hash code of the service:
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 =======
The phone rotation occurs midway where the lifecycle activity occurs. I have been working on this issue and searching online for the last two days for help but the only answer I see in relation to this problem is, "You can not create multiple instances of a service" But the log seems to prove otherwise. I've been reluctant to ask on stackoverflow since I'm new to it and don't fully know the proper way to ask the questions but I don't know what else to try. Please let me know if there is something I forgot to include in my question, thanks.
Mainifest:
<?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>
MainAcivity:
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()
}
}
After many hours of debugging I found the problem:
In MainActivity.onDestroy() I was attempting to stop my Service LocationService. The problem is that I attach a location callback to the Service and therefore the Service is leaked when the phone is rotated because MainActivity.onDestroy() is called which attempts to stop the service but can't apparently because of the attached Location Listener and Android seems to assume that it successfully stopped the service and so creates a new instance of it the next time around.
*** Seems to be an Android OS Bug ***