简体   繁体   中英

Android - How do I get location updates in the background in a service

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
}

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:

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM