Android 8 或 9 的后台地理围栏不起作用
Geofencing in background on Android 8 or 9 does not work
我尝试在用户到达指定区域时向其显示推送警报。
所以我编写了我的应用程序:https://developer.android.com/training/location/geofencing
如果我的应用程序是 运行 跟踪用户位置的服务,它会完美运行。
例如,如果我启动 google 地图,它也会起作用,它也会跟踪我的位置。将出现推送。
但是如果我关闭我的应用程序,推送将不会出现,因此如果没有应用程序跟踪我的位置,则不会检测到地理围栏。
正常吗?
如何让它一直工作?
如果您需要根据您的位置提供前台服务,地理围栏有什么意义?
public void createGeofenceAlerts(LatLng latLng, int radius) {
final Geofence enter = buildGeofence(ID_ENTER, latLng, radius, Geofence.GEOFENCE_TRANSITION_ENTER);
final Geofence exit = buildGeofence(ID_EXIT, latLng, radius, Geofence.GEOFENCE_TRANSITION_EXIT);
final Geofence dwell = buildGeofence(ID_DWELL, latLng, radius, Geofence.GEOFENCE_TRANSITION_DWELL);
GeofencingRequest request = new GeofencingRequest.Builder()
.setInitialTrigger(GeofencingRequest.INITIAL_TRIGGER_ENTER)
.addGeofence(enter)
.addGeofence(exit)
.addGeofence(dwell)
.build();
fencingClient.addGeofences(request, getGeofencePendingIntent()).addOnSuccessListener(new OnSuccessListener<Void>() {
@Override
public void onSuccess(Void aVoid) {
Timber.i("succes");
Toast.makeText(mContext, "Geofence added", Toast.LENGTH_LONG).show();
}
}).addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(@NonNull Exception e) {
Timber.e(e,"failure");
Toast.makeText(mContext, "Geofence ERROR", Toast.LENGTH_LONG).show();
}
});
}
private PendingIntent getGeofencePendingIntent() {
Intent intent = new Intent(mContext, GeofenceTransitionsIntentService.class);
PendingIntent pending = PendingIntent.getService(
mContext,
0,
intent,
PendingIntent.FLAG_UPDATE_CURRENT);
return pending;
}
private Geofence buildGeofence(String id, LatLng center, int radius, int transitionType) {
Geofence.Builder builder = new Geofence.Builder()
// 1
.setRequestId(id)
// 2
.setCircularRegion(
center.getLatitude(),
center.getLongitude(),
radius)
// 3
.setTransitionTypes(transitionType)
// 4
.setExpirationDuration(Geofence.NEVER_EXPIRE);
if (transitionType == Geofence.GEOFENCE_TRANSITION_DWELL) {
builder.setLoiteringDelay(LOITERING_DELAY);
}
return builder.build();
}
我使用 GeoFence 已经很长时间了,我有同样的问题,在尝试不同的解决方案后我自己得到了答案,所以基本上,GeoFence 只有在 [=24= 中的任何应用程序时才会触发] 正在获取某个 x 持续时间的位置。如果您测试 google 提供的 GeoFence 示例应用程序,那么您可以看到该应用程序仅在您打开 Google 地图应用程序时才能运行,这是因为 Google 地图是设备中唯一的应用程序被动地请求位置。
为了证明,您可以从下面的 GeoFence 示例和 LocationUpdateForGroundService 示例中克隆 link
https://github.com/googlesamples/android-play-location
运行 同时使用 GeoFence 和 LocationUpdateForGroundService,通过更改模拟器中的纬度和经度,您会注意到现在您不再需要打开 Google 地图,因为现在有另一个应用程序可以正在请求定位。
因此请务必在 GeoFence 应用程序中创建一个前台服务,并使用 Fuse Location Client 请求位置更新一段时间。
我想我找到了一个解决方案,在 Android 9 上进行了测试。我使用了 Google 文档 https://developer.android.com/training/location/geofencing 但我用广播接收器替换了该服务。
我的地理围栏管理器:
private val braodcastPendingIntent: PendingIntent
get() {
val intent = Intent(mContext, GeofenceTransitionsBroadcastReceiver::class.java)
val pending = PendingIntent.getBroadcast(
mContext.applicationContext,
0,
intent,
PendingIntent.FLAG_UPDATE_CURRENT)
return pending
}
fun createGeofenceAlerts(latLng: LatLng, radiusMeter: Int, isBroadcast: Boolean) {
val enter = buildGeofence(ID_ENTER, latLng, radiusMeter, Geofence.GEOFENCE_TRANSITION_ENTER)
val exit = buildGeofence(ID_EXIT, latLng, radiusMeter, Geofence.GEOFENCE_TRANSITION_EXIT)
val dwell = buildGeofence(ID_DWELL, latLng, radiusMeter, Geofence.GEOFENCE_TRANSITION_DWELL)
val request = GeofencingRequest.Builder()
.setInitialTrigger(GeofencingRequest.INITIAL_TRIGGER_ENTER)
.addGeofence(enter)
.addGeofence(exit)
.addGeofence(dwell)
.build()
val pending = if (isBroadcast) {
braodcastPendingIntent
} else {
servicePendingIntent
}
fencingClient.addGeofences(request, pending).addOnSuccessListener {
Timber.i("succes")
Toast.makeText(mContext, "Geofence added", Toast.LENGTH_LONG).show()
}.addOnFailureListener { e ->
Timber.e(e, "failure")
Toast.makeText(mContext, "Geofence ERROR", Toast.LENGTH_LONG).show()
}
}
private fun buildGeofence(id: String, center: LatLng, radius: Int, transitionType: Int): Geofence {
val builder = Geofence.Builder()
// 1
.setRequestId(id)
// 2
.setCircularRegion(
center.latitude,
center.longitude,
radius.toFloat())
// 3
.setTransitionTypes(transitionType)
// 4
.setExpirationDuration(Geofence.NEVER_EXPIRE)
if (transitionType == Geofence.GEOFENCE_TRANSITION_DWELL) {
builder.setLoiteringDelay(LOITERING_DELAY)
}
return builder.build()
}
我的BroadcastReceiver,显然你需要在manfifest中声明它:
class GeofenceTransitionsBroadcastReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
Timber.i("received")
val geofencingEvent = GeofencingEvent.fromIntent(intent)
if (geofencingEvent.hasError()) {
Timber.e("Geofence error")
return
}
// Get the transition type.
val geofenceTransition = geofencingEvent.geofenceTransition
// Test that the reported transition was of interest.
if (geofenceTransition == Geofence.GEOFENCE_TRANSITION_ENTER || geofenceTransition == Geofence.GEOFENCE_TRANSITION_EXIT
|| geofenceTransition == Geofence.GEOFENCE_TRANSITION_DWELL) {
// Get the geofences that were triggered. A single event can trigger
// multiple geofences.
val triggeringGeofences = geofencingEvent.triggeringGeofences
// Get the transition details as a String.
val geofenceTransitionDetails = GeofenceManager.getGeofenceTransitionDetails(
geofenceTransition,
triggeringGeofences, true
)
// Send notification and log the transition details.
GeofenceManager.sendNotification(context, geofenceTransition, geofenceTransitionDetails)
Timber.i(geofenceTransitionDetails)
} else {
// Log the error.
Timber.e("Unknown geo event : %d", geofenceTransition)
}
}
重要的是要知道在 Android 8 和 9 上地理围栏有 2 分钟的延迟。
O.S 将不允许应用从后台获取位置更新。在前台服务上实施地理围栏位置代码。它将运行如预期
我发现当我打开 google 地图时,我的 geoFenceBroadCastReceiver 开始在模拟器中正常工作。我根本无法弄清楚问题出在哪里,事实证明,我显然漏掉了一块拼图。此问题(标题恰当)中也描述了该行为:Geofence Broadcast Receiver not triggered but when I open the google map, it works, as well as filed numerous times as an issue in the Android Location Samples project Issue 239, Issue 247 Issue 266.
我没有看到这个问题的实际答案作为答案发布在这里,所以为了后代我会提供一些建议。 Issue 264 seems to point to the solution, i.e. using a JobIntentService
但是
GeoFence 示例 LocationUpdateIntentService 中有一个 code comment
Note: Apps running on "O" devices (regardless of targetSdkVersion) may
receive updates less frequently than the interval specified in the
{@link com.google.android.gms.location.LocationRequest}
when the app is no longer in the foreground.
这似乎证实了@Vinayak 在他的 answer here
中指出的内容
O.S will not allow app to get location update from background. Implement geofence location code on foreground service. It will run as expected
不允许地理围栏广播接收器及时获取位置更新(显然即使在 IntentService 中也是如此)。您必须 运行 在请求精细位置访问的前台服务中定位客户端以获得更多 accurate/timely geoFence 触发。所以看似正确的答案是 运行 foreGroundService.
中的地理围栏
如果您选择 foreGroundService 路线,您还需要创建一个通知通道,否则,您将 运行 遇到问题 like this one。
另请参阅:
https://developer.android.com/guide/components/foreground-services
https://developer.android.com/training/location/change-location-settings#location-request
https://developer.android.com/training/location/request-updates
我尝试在用户到达指定区域时向其显示推送警报。
所以我编写了我的应用程序:https://developer.android.com/training/location/geofencing
如果我的应用程序是 运行 跟踪用户位置的服务,它会完美运行。
例如,如果我启动 google 地图,它也会起作用,它也会跟踪我的位置。将出现推送。
但是如果我关闭我的应用程序,推送将不会出现,因此如果没有应用程序跟踪我的位置,则不会检测到地理围栏。
正常吗? 如何让它一直工作? 如果您需要根据您的位置提供前台服务,地理围栏有什么意义?
public void createGeofenceAlerts(LatLng latLng, int radius) {
final Geofence enter = buildGeofence(ID_ENTER, latLng, radius, Geofence.GEOFENCE_TRANSITION_ENTER);
final Geofence exit = buildGeofence(ID_EXIT, latLng, radius, Geofence.GEOFENCE_TRANSITION_EXIT);
final Geofence dwell = buildGeofence(ID_DWELL, latLng, radius, Geofence.GEOFENCE_TRANSITION_DWELL);
GeofencingRequest request = new GeofencingRequest.Builder()
.setInitialTrigger(GeofencingRequest.INITIAL_TRIGGER_ENTER)
.addGeofence(enter)
.addGeofence(exit)
.addGeofence(dwell)
.build();
fencingClient.addGeofences(request, getGeofencePendingIntent()).addOnSuccessListener(new OnSuccessListener<Void>() {
@Override
public void onSuccess(Void aVoid) {
Timber.i("succes");
Toast.makeText(mContext, "Geofence added", Toast.LENGTH_LONG).show();
}
}).addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(@NonNull Exception e) {
Timber.e(e,"failure");
Toast.makeText(mContext, "Geofence ERROR", Toast.LENGTH_LONG).show();
}
});
}
private PendingIntent getGeofencePendingIntent() {
Intent intent = new Intent(mContext, GeofenceTransitionsIntentService.class);
PendingIntent pending = PendingIntent.getService(
mContext,
0,
intent,
PendingIntent.FLAG_UPDATE_CURRENT);
return pending;
}
private Geofence buildGeofence(String id, LatLng center, int radius, int transitionType) {
Geofence.Builder builder = new Geofence.Builder()
// 1
.setRequestId(id)
// 2
.setCircularRegion(
center.getLatitude(),
center.getLongitude(),
radius)
// 3
.setTransitionTypes(transitionType)
// 4
.setExpirationDuration(Geofence.NEVER_EXPIRE);
if (transitionType == Geofence.GEOFENCE_TRANSITION_DWELL) {
builder.setLoiteringDelay(LOITERING_DELAY);
}
return builder.build();
}
我使用 GeoFence 已经很长时间了,我有同样的问题,在尝试不同的解决方案后我自己得到了答案,所以基本上,GeoFence 只有在 [=24= 中的任何应用程序时才会触发] 正在获取某个 x 持续时间的位置。如果您测试 google 提供的 GeoFence 示例应用程序,那么您可以看到该应用程序仅在您打开 Google 地图应用程序时才能运行,这是因为 Google 地图是设备中唯一的应用程序被动地请求位置。
为了证明,您可以从下面的 GeoFence 示例和 LocationUpdateForGroundService 示例中克隆 link https://github.com/googlesamples/android-play-location 运行 同时使用 GeoFence 和 LocationUpdateForGroundService,通过更改模拟器中的纬度和经度,您会注意到现在您不再需要打开 Google 地图,因为现在有另一个应用程序可以正在请求定位。
因此请务必在 GeoFence 应用程序中创建一个前台服务,并使用 Fuse Location Client 请求位置更新一段时间。
我想我找到了一个解决方案,在 Android 9 上进行了测试。我使用了 Google 文档 https://developer.android.com/training/location/geofencing 但我用广播接收器替换了该服务。
我的地理围栏管理器:
private val braodcastPendingIntent: PendingIntent
get() {
val intent = Intent(mContext, GeofenceTransitionsBroadcastReceiver::class.java)
val pending = PendingIntent.getBroadcast(
mContext.applicationContext,
0,
intent,
PendingIntent.FLAG_UPDATE_CURRENT)
return pending
}
fun createGeofenceAlerts(latLng: LatLng, radiusMeter: Int, isBroadcast: Boolean) {
val enter = buildGeofence(ID_ENTER, latLng, radiusMeter, Geofence.GEOFENCE_TRANSITION_ENTER)
val exit = buildGeofence(ID_EXIT, latLng, radiusMeter, Geofence.GEOFENCE_TRANSITION_EXIT)
val dwell = buildGeofence(ID_DWELL, latLng, radiusMeter, Geofence.GEOFENCE_TRANSITION_DWELL)
val request = GeofencingRequest.Builder()
.setInitialTrigger(GeofencingRequest.INITIAL_TRIGGER_ENTER)
.addGeofence(enter)
.addGeofence(exit)
.addGeofence(dwell)
.build()
val pending = if (isBroadcast) {
braodcastPendingIntent
} else {
servicePendingIntent
}
fencingClient.addGeofences(request, pending).addOnSuccessListener {
Timber.i("succes")
Toast.makeText(mContext, "Geofence added", Toast.LENGTH_LONG).show()
}.addOnFailureListener { e ->
Timber.e(e, "failure")
Toast.makeText(mContext, "Geofence ERROR", Toast.LENGTH_LONG).show()
}
}
private fun buildGeofence(id: String, center: LatLng, radius: Int, transitionType: Int): Geofence {
val builder = Geofence.Builder()
// 1
.setRequestId(id)
// 2
.setCircularRegion(
center.latitude,
center.longitude,
radius.toFloat())
// 3
.setTransitionTypes(transitionType)
// 4
.setExpirationDuration(Geofence.NEVER_EXPIRE)
if (transitionType == Geofence.GEOFENCE_TRANSITION_DWELL) {
builder.setLoiteringDelay(LOITERING_DELAY)
}
return builder.build()
}
我的BroadcastReceiver,显然你需要在manfifest中声明它:
class GeofenceTransitionsBroadcastReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
Timber.i("received")
val geofencingEvent = GeofencingEvent.fromIntent(intent)
if (geofencingEvent.hasError()) {
Timber.e("Geofence error")
return
}
// Get the transition type.
val geofenceTransition = geofencingEvent.geofenceTransition
// Test that the reported transition was of interest.
if (geofenceTransition == Geofence.GEOFENCE_TRANSITION_ENTER || geofenceTransition == Geofence.GEOFENCE_TRANSITION_EXIT
|| geofenceTransition == Geofence.GEOFENCE_TRANSITION_DWELL) {
// Get the geofences that were triggered. A single event can trigger
// multiple geofences.
val triggeringGeofences = geofencingEvent.triggeringGeofences
// Get the transition details as a String.
val geofenceTransitionDetails = GeofenceManager.getGeofenceTransitionDetails(
geofenceTransition,
triggeringGeofences, true
)
// Send notification and log the transition details.
GeofenceManager.sendNotification(context, geofenceTransition, geofenceTransitionDetails)
Timber.i(geofenceTransitionDetails)
} else {
// Log the error.
Timber.e("Unknown geo event : %d", geofenceTransition)
}
}
重要的是要知道在 Android 8 和 9 上地理围栏有 2 分钟的延迟。
O.S 将不允许应用从后台获取位置更新。在前台服务上实施地理围栏位置代码。它将运行如预期
我发现当我打开 google 地图时,我的 geoFenceBroadCastReceiver 开始在模拟器中正常工作。我根本无法弄清楚问题出在哪里,事实证明,我显然漏掉了一块拼图。此问题(标题恰当)中也描述了该行为:Geofence Broadcast Receiver not triggered but when I open the google map, it works, as well as filed numerous times as an issue in the Android Location Samples project Issue 239, Issue 247 Issue 266.
我没有看到这个问题的实际答案作为答案发布在这里,所以为了后代我会提供一些建议。 Issue 264 seems to point to the solution, i.e. using a JobIntentService
但是
GeoFence 示例 LocationUpdateIntentService 中有一个 code comment
Note: Apps running on "O" devices (regardless of targetSdkVersion) may receive updates less frequently than the interval specified in the {@link com.google.android.gms.location.LocationRequest} when the app is no longer in the foreground.
这似乎证实了@Vinayak 在他的 answer here
中指出的内容O.S will not allow app to get location update from background. Implement geofence location code on foreground service. It will run as expected
不允许地理围栏广播接收器及时获取位置更新(显然即使在 IntentService 中也是如此)。您必须 运行 在请求精细位置访问的前台服务中定位客户端以获得更多 accurate/timely geoFence 触发。所以看似正确的答案是 运行 foreGroundService.
中的地理围栏如果您选择 foreGroundService 路线,您还需要创建一个通知通道,否则,您将 运行 遇到问题 like this one。
另请参阅:
https://developer.android.com/guide/components/foreground-services
https://developer.android.com/training/location/change-location-settings#location-request
https://developer.android.com/training/location/request-updates