地理围栏结合了用户当前位置感知以及用户与感兴趣位置距离的感知。要标记感兴趣的位置,需要指定其纬度和经度。要调整该位置的邻近范围,需要添加半径。纬度、经度和半径共同定义了一个地理围栏,在感兴趣的位置周围创建了一个圆形区域或“围栏”。
您可以拥有多个活动的地理围栏,每个应用每个设备用户最多 100 个。对于每个地理围栏,您可以请求 Location Services 向您发送进入和退出事件,或者指定在地理围栏区域内等待或“停留”一段时间后再触发事件。您可以通过指定毫秒为单位的过期时长来限制任何地理围栏的持续时间。地理围栏过期后,Location Services 会自动将其移除。

本课程介绍了如何添加和移除地理围栏,然后使用 BroadcastReceiver
监听地理围栏转换。
注意:在 Wear 设备上,Geofencing API 对电源的使用效率不高。不建议在 Wear 上使用这些 API。请阅读节省电量和电池了解更多信息。
设置地理围栏监控
请求地理围栏监控的第一步是请求必要的权限。要使用地理围栏,您的应用必须请求以下权限:
-
ACCESS_FINE_LOCATION
-
ACCESS_BACKGROUND_LOCATION
(如果您的应用目标平台是 Android 10(API 级别 29)或更高版本)
要了解更多信息,请参阅关于如何请求位置权限的指南。
如果您想使用 BroadcastReceiver
监听地理围栏转换,请添加一个元素指定服务名称。此元素必须是 <application>
元素的子元素
<application android:allowBackup="true"> ... <receiver android:name=".GeofenceBroadcastReceiver"/> <application/>
要访问位置 API,您需要创建 Geofencing 客户端的实例。了解如何连接您的客户端
Kotlin
lateinit var geofencingClient: GeofencingClient override fun onCreate(savedInstanceState: Bundle?) { // ... geofencingClient = LocationServices.getGeofencingClient(this) }
Java
private GeofencingClient geofencingClient; @Override public void onCreate(Bundle savedInstanceState) { // ... geofencingClient = LocationServices.getGeofencingClient(this); }
创建和添加地理围栏
您的应用需要使用位置 API 中用于创建 Geofence 对象的构建器类以及用于添加它们的便捷类来创建和添加地理围栏。此外,为了处理 Location Services 在地理围栏转换发生时发送的 Intent,您可以按本节所示定义一个 PendingIntent
。
注意:在单用户设备上,每个应用最多可设置 100 个地理围栏。对于多用户设备,每个应用每个设备用户最多可设置 100 个地理围栏。
创建地理围栏对象
首先,使用 Geofence.Builder
创建一个地理围栏,设置所需的半径、持续时间和地理围栏转换类型。例如,填充列表对象
Kotlin
geofenceList.add(Geofence.Builder() // Set the request ID of the geofence. This is a string to identify this // geofence. .setRequestId(entry.key) // Set the circular region of this geofence. .setCircularRegion( entry.value.latitude, entry.value.longitude, Constants.GEOFENCE_RADIUS_IN_METERS ) // Set the expiration duration of the geofence. This geofence gets automatically // removed after this period of time. .setExpirationDuration(Constants.GEOFENCE_EXPIRATION_IN_MILLISECONDS) // Set the transition types of interest. Alerts are only generated for these // transition. We track entry and exit transitions in this sample. .setTransitionTypes(Geofence.GEOFENCE_TRANSITION_ENTER or Geofence.GEOFENCE_TRANSITION_EXIT) // Create the geofence. .build())
Java
geofenceList.add(new Geofence.Builder() // Set the request ID of the geofence. This is a string to identify this // geofence. .setRequestId(entry.getKey()) .setCircularRegion( entry.getValue().latitude, entry.getValue().longitude, Constants.GEOFENCE_RADIUS_IN_METERS ) .setExpirationDuration(Constants.GEOFENCE_EXPIRATION_IN_MILLISECONDS) .setTransitionTypes(Geofence.GEOFENCE_TRANSITION_ENTER | Geofence.GEOFENCE_TRANSITION_EXIT) .build());
此示例从常量文件中提取数据。在实际应用中,应用可能会根据用户位置动态创建地理围栏。
指定地理围栏和初始触发器
以下代码片段使用 GeofencingRequest
类及其嵌套的 GeofencingRequestBuilder
类来指定要监控的地理围栏以及设置如何触发相关的地理围栏事件
Kotlin
private fun getGeofencingRequest(): GeofencingRequest { return GeofencingRequest.Builder().apply { setInitialTrigger(GeofencingRequest.INITIAL_TRIGGER_ENTER) addGeofences(geofenceList) }.build() }
Java
private GeofencingRequest getGeofencingRequest() { GeofencingRequest.Builder builder = new GeofencingRequest.Builder(); builder.setInitialTrigger(GeofencingRequest.INITIAL_TRIGGER_ENTER); builder.addGeofences(geofenceList); return builder.build(); }
此示例展示了两种地理围栏触发器的使用。 GEOFENCE_TRANSITION_ENTER
转换在设备进入地理围栏时触发, GEOFENCE_TRANSITION_EXIT
转换在设备退出地理围栏时触发。指定 INITIAL_TRIGGER_ENTER
会告诉 Location Services,如果设备已在地理围栏内,则应触发 GEOFENCE_TRANSITION_ENTER
。
在许多情况下,最好改用 INITIAL_TRIGGER_DWELL
,它仅在用户在地理围栏内停留特定时间后才触发事件。这种方法有助于减少设备短暂进入和退出地理围栏时产生大量通知造成的“警报垃圾邮件”。另一种从地理围栏获得最佳结果的策略是将最小半径设置为 100 米。这有助于考虑典型 Wi-Fi 网络的位置精度,并且还有助于降低设备功耗。
定义用于地理围栏转换的广播接收器
Location Services 发送的 Intent
可以在您的应用中触发各种操作,但您不应让它启动 Activity 或 Fragment,因为组件只应响应用户操作而变得可见。在许多情况下,BroadcastReceiver
是处理地理围栏转换的好方法。BroadcastReceiver
在事件发生时(例如进入或退出地理围栏的转换)接收更新,并可以启动长时间运行的后台工作。
以下代码片段展示了如何定义一个启动 BroadcastReceiver
的 PendingIntent
Kotlin
class MainActivity : AppCompatActivity() { // ... private val geofencePendingIntent: PendingIntent by lazy { val intent = Intent(this, GeofenceBroadcastReceiver::class.java) // We use FLAG_UPDATE_CURRENT so that we get the same pending intent back when calling // addGeofences() and removeGeofences(). PendingIntent.getBroadcast(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT) } }
Java
public class MainActivity extends AppCompatActivity { // ... private PendingIntent getGeofencePendingIntent() { // Reuse the PendingIntent if we already have it. if (geofencePendingIntent != null) { return geofencePendingIntent; } Intent intent = new Intent(this, GeofenceBroadcastReceiver.class); // We use FLAG_UPDATE_CURRENT so that we get the same pending intent back when // calling addGeofences() and removeGeofences(). geofencePendingIntent = PendingIntent.getBroadcast(this, 0, intent, PendingIntent. FLAG_UPDATE_CURRENT); return geofencePendingIntent; }
添加地理围栏
要添加地理围栏,请使用
方法。提供 GeofencingClient.addGeofences()
GeofencingRequest
对象和 PendingIntent
。以下代码片段展示了处理结果的过程
Kotlin
geofencingClient?.addGeofences(getGeofencingRequest(), geofencePendingIntent)?.run { addOnSuccessListener { // Geofences added // ... } addOnFailureListener { // Failed to add geofences // ... } }
Java
geofencingClient.addGeofences(getGeofencingRequest(), getGeofencePendingIntent()) .addOnSuccessListener(this, new OnSuccessListener<Void>() { @Override public void onSuccess(Void aVoid) { // Geofences added // ... } }) .addOnFailureListener(this, new OnFailureListener() { @Override public void onFailure(@NonNull Exception e) { // Failed to add geofences // ... } });
处理地理围栏转换
当 Location Services 检测到用户进入或退出地理围栏时,它会发送包含您在添加地理围栏请求中包含的 PendingIntent
的 Intent
。像 GeofenceBroadcastReceiver
这样的广播接收器会注意到 Intent
被调用,然后可以从 Intent 中获取地理围栏事件,确定地理围栏转换的类型,并确定哪些定义的地理围栏被触发。广播接收器可以指示应用开始执行后台工作,或者,如果需要,发送通知作为输出。
注意:在 Android 8.0(API 级别 26)及更高版本上,如果应用在监控地理围栏时在后台运行,则设备每隔几分钟响应一次地理围栏事件。要了解如何让您的应用适应这些响应限制,请参阅后台位置限制。
以下代码片段展示了如何定义一个在地理围栏转换发生时发布通知的 BroadcastReceiver
。当用户点击通知时,会显示应用的主 Activity
Kotlin
class GeofenceBroadcastReceiver : BroadcastReceiver() { // ... override fun onReceive(context: Context?, intent: Intent?) { val geofencingEvent = GeofencingEvent.fromIntent(intent) if (geofencingEvent.hasError()) { val errorMessage = GeofenceStatusCodes .getStatusCodeString(geofencingEvent.errorCode) Log.e(TAG, errorMessage) 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) { // 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 = getGeofenceTransitionDetails( this, geofenceTransition, triggeringGeofences ) // Send notification and log the transition details. sendNotification(geofenceTransitionDetails) Log.i(TAG, geofenceTransitionDetails) } else { // Log the error. Log.e(TAG, getString(R.string.geofence_transition_invalid_type, geofenceTransition)) } } }
Java
public class GeofenceBroadcastReceiver extends BroadcastReceiver { // ... protected void onReceive(Context context, Intent intent) { GeofencingEvent geofencingEvent = GeofencingEvent.fromIntent(intent); if (geofencingEvent.hasError()) { String errorMessage = GeofenceStatusCodes .getStatusCodeString(geofencingEvent.getErrorCode()); Log.e(TAG, errorMessage); return; } // Get the transition type. int geofenceTransition = geofencingEvent.getGeofenceTransition(); // Test that the reported transition was of interest. if (geofenceTransition == Geofence.GEOFENCE_TRANSITION_ENTER || geofenceTransition == Geofence.GEOFENCE_TRANSITION_EXIT) { // Get the geofences that were triggered. A single event can trigger // multiple geofences. List<Geofence> triggeringGeofences = geofencingEvent.getTriggeringGeofences(); // Get the transition details as a String. String geofenceTransitionDetails = getGeofenceTransitionDetails( this, geofenceTransition, triggeringGeofences ); // Send notification and log the transition details. sendNotification(geofenceTransitionDetails); Log.i(TAG, geofenceTransitionDetails); } else { // Log the error. Log.e(TAG, getString(R.string.geofence_transition_invalid_type, geofenceTransition)); } } }
通过 PendingIntent
检测到转换事件后,BroadcastReceiver
获取地理围栏转换类型并测试它是否是应用用于触发通知的事件之一——在此示例中是 GEOFENCE_TRANSITION_ENTER
或 GEOFENCE_TRANSITION_EXIT
。然后,该服务发送通知并记录转换详细信息。
停止地理围栏监控
当不再需要或不希望进行地理围栏监控时停止它,有助于节省设备的电池电量和 CPU 周期。您可以在用于添加和移除地理围栏的主 Activity 中停止地理围栏监控;移除地理围栏会立即停止监控。API 提供了通过请求 ID 或移除与给定 PendingIntent
关联的地理围栏的方法。
以下代码片段通过 PendingIntent
移除地理围栏,从而在设备进入或退出先前添加的地理围栏时停止所有进一步通知
Kotlin
geofencingClient?.removeGeofences(geofencePendingIntent)?.run { addOnSuccessListener { // Geofences removed // ... } addOnFailureListener { // Failed to remove geofences // ... } }
Java
geofencingClient.removeGeofences(getGeofencePendingIntent()) .addOnSuccessListener(this, new OnSuccessListener<Void>() { @Override public void onSuccess(Void aVoid) { // Geofences removed // ... } }) .addOnFailureListener(this, new OnFailureListener() { @Override public void onFailure(@NonNull Exception e) { // Failed to remove geofences // ... } });
您可以将地理围栏与其他位置感知功能结合使用,例如周期性位置更新。有关详细信息,请参阅本课程中的其他课程。
使用地理围栏的最佳实践
本节概述了使用 Android 位置 API 进行地理围栏的建议。
降低功耗
您可以使用以下技术来优化使用地理围栏的应用的功耗:
将通知响应速度设置为更高的值。这样做可以通过增加地理围栏警报的延迟来提高功耗效率。例如,如果您将响应速度值设置为五分钟,则您的应用每五分钟只检查一次进入或退出警报。设置较低的值不一定意味着用户会在该时间段内收到通知(例如,如果您将值设置为 5 秒,则收到警报可能需要比这更长的时间)。
对于用户花费大量时间的地点(例如家庭或工作地点),使用更大的地理围栏半径。虽然更大的半径不能直接降低功耗,但它可以减少应用检查进入或退出频率,从而有效地降低整体功耗。
选择最佳地理围栏半径
为获得最佳效果,地理围栏的最小半径应设置为 100 - 150 米之间。当 Wi-Fi 可用时,位置精度通常在 20 - 50 米之间。当室内位置可用时,精度范围可以小至 5 米。除非您知道地理围栏内有室内位置可用,否则假设 Wi-Fi 位置精度约为 50 米。
当 Wi-Fi 位置不可用时(例如,在农村地区驾车时),位置精度会降低。精度范围可能大到几百米到几公里。在这种情况下,您应该使用更大的半径创建地理围栏。
向用户解释您的应用为何使用地理围栏
由于您的应用在使用地理围栏时会在后台访问位置信息,因此请考虑您的应用如何为用户带来益处。清晰地向他们解释您的应用为何需要此权限,以提高用户的理解和透明度。
有关位置访问(包括地理围栏)相关最佳实践的更多信息,请参阅隐私权最佳实践页面。
使用停留转换类型减少警报垃圾邮件
如果您在驾车短暂经过地理围栏时收到大量警报,减少警报的最佳方法是使用 GEOFENCE_TRANSITION_DWELL
转换类型,而不是 GEOFENCE_TRANSITION_ENTER
。这样一来,只有当用户在地理围栏内停留指定时间后,才会发送停留警报。您可以通过设置逗留延迟来选择时长。
仅在需要时重新注册地理围栏
注册的地理围栏保存在由 com.google.android.gms
软件包拥有的 com.google.process.location
进程中。应用无需执行任何操作来处理以下事件,因为系统会在这些事件后恢复地理围栏:
- Google Play services 已升级。
- Google Play services 由于资源限制而被系统终止并重新启动。
- 位置进程崩溃。
如果地理围栏在以下事件后仍然需要,则应用必须重新注册它们,因为在以下情况下系统无法恢复地理围栏:
- 设备已重新启动。应用应监听设备的启动完成操作,然后重新注册所需的地理围栏。
- 应用已卸载并重新安装。
- 应用的数据已清除。
- Google Play services 数据已清除。
- 应用已收到
GEOFENCE_NOT_AVAILABLE
警报。这通常发生在 NLP(Android 的网络位置提供商)被禁用后。
排查地理围栏进入事件
如果设备进入地理围栏时未触发地理围栏(未触发 GEOFENCE_TRANSITION_ENTER
警报),请首先确保您的地理围栏已按照本指南的说明正确注册。
以下是一些导致警报未按预期工作的可能原因:
- 您的地理围栏内没有准确位置或您的地理围栏太小。 在大多数设备上,地理围栏服务仅使用网络位置进行地理围栏触发。该服务之所以采用此方法,是因为网络位置消耗的电量少得多,获取离散位置所需的时间更短,最重要的是,室内位置可用。
设备上的 Wi-Fi 已关闭。 打开 Wi-Fi 可以显著提高位置精度,因此如果 Wi-Fi 关闭,您的应用可能永远无法收到地理围栏警报,具体取决于多种设置,包括地理围栏半径、设备型号或 Android 版本。从 Android 4.3(API 级别 18)开始,我们添加了“仅 Wi-Fi 扫描模式”功能,允许用户禁用 Wi-Fi 但仍获得良好的网络位置。如果 Wi-Fi 和仅 Wi-Fi 扫描模式都已禁用,最好提示用户并提供快捷方式供用户启用它们。使用 SettingsClient 确保设备的系统设置已正确配置以实现最佳位置检测。
注意:如果您的应用目标平台为 Android 10(API 级别 29)或更高版本,则不能直接调用
WifiManager.setEnabled()
,除非您的应用是系统应用或设备策略控制器 (DPC)。请改用设置面板。- 您的地理围栏内没有可靠的网络连接。 如果没有可靠的数据连接,可能不会生成警报。这是因为地理围栏服务依赖于网络位置提供商,而网络位置提供商需要数据连接。
- 警报可能会延迟。 地理围栏服务不会持续查询位置,因此收到警报时会预期一些延迟。通常,延迟时间少于 2 分钟,即使设备正在移动,延迟时间更短。如果后台位置限制生效,平均延迟时间约为 2-3 分钟。如果设备长时间处于静止状态,延迟时间可能会增加(最多 6 分钟)。
其他资源
要了解有关地理围栏的更多信息,请参阅以下材料: