-1

I need to access location in the background when my app is running.

I successfully received location updates when my main activity implemented android.location.LocationListener

if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
    val locationManager = getSystemService(Context.LOCATION_SERVICE) as LocationManager
    val criteria = Criteria()
    val provider = locationManager.getBestProvider(criteria, false)
    val location = locationManager.getLastKnownLocation(provider)

    locationManager.requestLocationUpdates(provider, 5000, 20f, this)
    setLocation(location)
} else {
    /* ... */
}

However I attempted to add this code to a new service class (implementing both android.app.Service and android.location.LocationListener), and the onLocationChanged function is only firing when the app is in view.

I believe this problem could be one of the following:

  1. I think I read somewhere that I have to manually create a new thread inside the service so that it does not run on the same thread as the main activity. This could be the issue, but I haven't a clue how to do this. I'm new to android development and Kotlin.

  2. I think I read somewhere that the location permissions I currently request aren't enough when accessing location in the background, but can't find out what to change.

  3. I saw a solution somewhere where they create a timer that runs every few seconds and manually requests the last location from the location manager. I haven't tested this, but if it does work it doesn't satisfy my needs. The requestLocationUpdates allows me to configure both a minimum time and minimum distance. Of course I could manually add in a distance check, but this could affect battery life and performance.

How do I fix this problem?

(I can probably change any Java code to Kotlin if anyone is not familiar with Kotlin)


Edit 1

  • My service is definitely running, as the location updates are received in the foreground

  • Here is the onStartCommand code for my service:

override fun onStartCommand(intent: Intent, flags: Int, startid: Int): Int {
    /* request location updates as above */
    return START_STICKY
}
  • Here is how I start the service in my main activity:
val serviceIntent = Intent(applicationContext, OnTheFlyLandmarkService::class.java)
startService(serviceIntent)

Edit 2

  1. I also read that I need to display a notification to let users know that my service is running in the background. I tried the following code but this did not help:

    val notification = NotificationCompat.Builder(this, CHANNEL_ID)
        .setSmallIcon(R.drawable.messageicon)
        .setContentTitle("My app")
        .setContentText("Accessing location in background...")
        .setAutoCancel(true)
        .setPriority(NotificationCompat.PRIORITY_DEFAULT)
        .build()
    
    with(NotificationManagerCompat.from(this)) {
        notify(id, notification)
    }
    

Edit 3

I tried what @Michael said: call startForeground inside the service and pass in the notification. This did not work, and I didn't even see a notification.

override fun onStartCommand(intent: Intent, flags: Int, startid: Int): Int {
    val notification = buildNotification("My app", "Accessing location in background...")
    startForeground(NOTIFICATION_ID_SERVICE, notification)

    /* request location updates */
    return START_STICKY
}
David Callanan
  • 5,601
  • 7
  • 63
  • 105
  • Can you add the `Service` code you created? Is your `Service` started with run in foreground? Is it `Sticky`? – madlymad Nov 17 '19 at 11:25
  • @madlymad I added code, it is a sticky service – David Callanan Nov 17 '19 at 11:37
  • 1
    Starting with Android 8.0, your app will only get a few location updates per hour if it is in the background. If that's not enough for you, then use a [foreground service](https://developer.android.com/guide/components/services.html#Foreground) instead. – Michael Nov 17 '19 at 11:40
  • @Michael this did not work for me. Thanks for all of your help so far – David Callanan Nov 17 '19 at 12:02

1 Answers1

2

For using a Service that always runs you have some important things:

  1. Add it to your manifest
    <manifest ... >
      ...
      <application ... >
          <service android:name=".ExampleService" />
          ...
      </application>
    </manifest>
  1. Make it START_STICKY so as the system to restart it, in case it kills it.
    override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
        Toast.makeText(this, "service starting", Toast.LENGTH_SHORT).show()
        // ...

        // If we get killed, after returning from here, restart
        return START_STICKY
    }
  1. Start it in foreground this is required for any app that target Android 9 (API level 28). Otherwise you get a SecurityException. Plus the permission in your manifest
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
  1. To pass location data between your service and your activity you may also consider the binding mechanism. (details into the second link, note that notification creation in the example is outdated without the channel)

You already have the 1 and 2. So you have to do the 3.

Use a foreground notification in your Service OnCreate method.

val pendingIntent: PendingIntent =
        Intent(this, ExampleActivity::class.java).let { notificationIntent ->
            PendingIntent.getActivity(this, 0, notificationIntent, 0)
        }

val notification: Notification = NotificationCompat.Builder(this, CHANNEL_ID)
.setSmallIcon(R.drawable.messageicon)
.setContentTitle("My app")
.setContentText("Accessing location in background...")
.setContentIntent(pendingIntent)
.setAutoCancel(true)
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.build()

startForeground(ONGOING_NOTIFICATION_ID, notification)
// Caution: The integer ID that you give to startForeground() must not be 0.

Check for more details the official documentation:

madlymad
  • 6,367
  • 6
  • 37
  • 68