Android - 如何在服务后台获取位置更新
Android - How do I get location updates in the background in a service
当我的应用 运行ning 时,我需要在后台访问位置。
当我的主要 activity 实施 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 {
/* ... */
}
但是我试图将此代码添加到新服务 class(同时实现 android.app.Service
和 android.location.LocationListener
),并且 onLocationChanged
函数仅在应用程序在视图中。
我认为这个问题可能是以下问题之一:
我想我在某处读到我必须在服务中手动创建一个新线程,这样它就不会 运行 与主 activity 在同一个线程上。这可能是问题所在,但我不知道如何执行此操作。我是 android 开发和 Kotlin 的新手。
我好像在某处读到,在后台访问位置时,我当前请求的位置权限不够,但找不到要更改的内容。
我在某个地方看到了一个解决方案,他们创建了一个计时器,每隔几秒 运行s 并手动从位置管理器请求最后一个位置。我没有测试过这个,但如果它确实有效,它不能满足我的需求。 requestLocationUpdates
允许我配置最短时间和最短距离。当然,我可以手动添加距离检查,但这可能会影响电池寿命和性能。
我该如何解决这个问题?
(如果有人不熟悉 Kotlin,我可能会将任何 Java 代码更改为 Kotlin)
编辑 1
我的服务肯定是 运行ning,因为位置更新是在前台接收的
这是我的服务的 onStartCommand
代码:
override fun onStartCommand(intent: Intent, flags: Int, startid: Int): Int {
/* request location updates as above */
return START_STICKY
}
- 这是我在主 activity 中启动服务的方式:
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
,您需要做一些重要的事情:
- 将其添加到您的清单
<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"/>
- 要在您的服务和 activity 之间传递位置数据,您还可以考虑绑定机制。 (详见第二个 link,请注意,示例中的通知创建在没有通道的情况下已经过时)
你已经有了 1 和 2。所以你必须做 3。
在您的服务 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.
查看官方文档了解更多详情:
当我的应用 运行ning 时,我需要在后台访问位置。
当我的主要 activity 实施 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 {
/* ... */
}
但是我试图将此代码添加到新服务 class(同时实现 android.app.Service
和 android.location.LocationListener
),并且 onLocationChanged
函数仅在应用程序在视图中。
我认为这个问题可能是以下问题之一:
我想我在某处读到我必须在服务中手动创建一个新线程,这样它就不会 运行 与主 activity 在同一个线程上。这可能是问题所在,但我不知道如何执行此操作。我是 android 开发和 Kotlin 的新手。
我好像在某处读到,在后台访问位置时,我当前请求的位置权限不够,但找不到要更改的内容。
我在某个地方看到了一个解决方案,他们创建了一个计时器,每隔几秒 运行s 并手动从位置管理器请求最后一个位置。我没有测试过这个,但如果它确实有效,它不能满足我的需求。
requestLocationUpdates
允许我配置最短时间和最短距离。当然,我可以手动添加距离检查,但这可能会影响电池寿命和性能。
我该如何解决这个问题?
(如果有人不熟悉 Kotlin,我可能会将任何 Java 代码更改为 Kotlin)
编辑 1
我的服务肯定是 运行ning,因为位置更新是在前台接收的
这是我的服务的
onStartCommand
代码:
override fun onStartCommand(intent: Intent, flags: Int, startid: Int): Int {
/* request location updates as above */
return START_STICKY
}
- 这是我在主 activity 中启动服务的方式:
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
,您需要做一些重要的事情:
- 将其添加到您的清单
<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"/>
- 要在您的服务和 activity 之间传递位置数据,您还可以考虑绑定机制。 (详见第二个 link,请注意,示例中的通知创建在没有通道的情况下已经过时)
你已经有了 1 和 2。所以你必须做 3。
在您的服务 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.
查看官方文档了解更多详情: