Android - 如何在服务的后台获取位置更新

Posted

技术标签:

【中文标题】Android - 如何在服务的后台获取位置更新【英文标题】:Android - How do I get location updates in the background in a service 【发布时间】:2020-03-12 22:59:50 【问题描述】:

当我的应用程序运行时,我需要在后台访问位置。

当我的主要活动实现时,我成功接收到位置更新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 
    /* ... */

但是我尝试将此代码添加到新的服务类(同时实现android.app.Serviceandroid.location.LocationListener),并且onLocationChanged 函数仅在应用程序处于视图中时触发。

我认为这个问题可能是以下问题之一:

    我想我在某处读到过,我必须在服务内手动创建一个新线程,这样它就不会在与主要活动相同的线程上运行。这可能是问题所在,但我不知道如何做到这一点。我是 android 开发和 Kotlin 的新手。

    我想我在某处读到,在后台访问位置时,我当前请求的位置权限不够,但找不到要更改的内容。

    我在某处看到了一个解决方案,他们创建了一个每隔几秒运行一次的计时器,并从位置管理器手动请求最后一个位置。我没有对此进行测试,但如果它确实有效,它并不能满足我的需求。 requestLocationUpdates 允许我配置最短时间和最短距离。当然我可以手动添加距离检查,但这可能会影响电池寿命和性能。

我该如何解决这个问题?

(如果有人不熟悉 Kotlin,我可以将任何 Java 代码更改为 Kotlin)


编辑 1

我的服务肯定在运行,因为位置更新是在前台接收的

这是我的服务的onStartCommand 代码:

override fun onStartCommand(intent: Intent, flags: Int, startid: Int): Int 
    /* request location updates as above */
    return START_STICKY

这是我在主要活动中启动服务的方式:
val serviceIntent = Intent(applicationContext, OnTheFlyLandmarkService::class.java)
startService(serviceIntent)

编辑 2

    我还读到我需要显示一条通知,让用户知道我的服务正在后台运行。我尝试了以下代码,但这没有帮助:

    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)
    
    

编辑 3

我尝试了@Michael 所说的:在服务内部调用startForeground 并传入通知。这不起作用,我什至没有看到通知。

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

【问题讨论】:

您可以添加您创建的Service 代码吗?你的Service 是从前台运行开始的吗?是Sticky吗? @madlymad 我添加了代码,这是一个粘性服务 从 Android 8.0 开始,如果您的应用在后台运行,它每小时只会获得几次位置更新。如果这对您来说还不够,请改用foreground service。 @Michael 这对我不起作用。感谢您迄今为止的所有帮助 【参考方案1】:

对于使用始终运行的Service,您有一些重要的事情:

    将其添加到您的清单中
    <manifest ... >
      ...
      <application ... >
          <service android:name=".ExampleService" />
          ...
      </application>
    </manifest>
    设置为START_STICKY,以便系统重新启动它,以防它杀死它。
    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
    
    在前台启动它对于任何面向 Android 9(API 级别 28)的应用程序都是必需的。否则你会得到一个SecurityException。加上清单中的权限
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
    要在服务和活动之间传递位置数据,您还可以考虑绑定机制。 (详情见第二个链接,请注意,示例中的通知创建在没有频道的情况下已过时

你已经有了 1 和 2。所以你必须做 3。

在您的 Service OnCreate 方法中使用前台通知。

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.

查看官方文档了解更多详情:

https://developer.android.com/guide/components/services https://developer.android.com/reference/android/app/Service

【讨论】:

由于某种原因未显示通知。 ONGOING_NOTIFICATION_ID 是一个特殊的 id 吗?我刚刚使用了 id 0。显示通知的意图是否必要? 根据文档,您可以使用除 0 之外的任何其他内容,因为使用 0 它不会出现;) 哈哈,谢谢一百万,我还需要添加FOREGROUND_SERVICE权限。 太棒了!为了完整,我还编辑了帖子! xD

以上是关于Android - 如何在服务的后台获取位置更新的主要内容,如果未能解决你的问题,请参考以下文章

在后台服务中使用融合位置获取GPS更新前(15分钟)?

如何在后台运行 android 服务 24x7 以获取用户的位置

如何从后台服务android更新谷歌地图v2位置

即使在后台,如何使用 Google 位置服务 API 来获取位置更新?

如何在 Android 中使用后台服务每 5 分钟获取一次设备位置?

从 Android 后台服务问题获取当前位置