13、地理围栏:Geofencing概念、Geofence.Builder构建、GeofencingRequest与广播接收
地理围栏,说白了就是在地图上画个虚拟的圈。手机一旦进入或离开这个圈,你的App就能收到通知。这功能在LBS应用里太常见了——比如你靠近星巴克,手机弹个优惠券;或者孩子离开学校区域,家长收到提醒。
我最早接触Geofencing是在做一个商场导购App时。当时需求是用户走进商场200米范围内,自动推送当天的促销活动。嗯,那时候我还不太熟悉这套API,踩了不少坑。今天咱们就把这块彻底讲透。
13.1 地理围栏的核心概念
先理清几个关键点:
- 围栏(Geofence):一个圆形区域,由经纬度+半径定义。半径最小可以到100米左右,再小精度就不太靠谱了。
- 触发事件:进入(ENTER)、离开(EXIT)、停留(DWELL)。停留需要设置一个超时时间,比如用户在里面待了5分钟才算停留。
- 响应延迟:系统不是实时检测的,而是根据设备位置更新频率来触发。省电模式下延迟可能高达几分钟。
我个人习惯把地理围栏理解成「虚拟围栏+事件监听器」的组合。你画好圈,告诉系统你想监听什么事件,剩下的交给Location Service去轮询。
13.2 Geofence.Builder:构建围栏对象
每个围栏都是一个Geofence对象。我们用Builder模式来创建它:
Geofence geofence = new Geofence.Builder()
.setRequestId("fence_home") // 唯一标识,用于区分不同围栏
.setCircularRegion(
39.9042, // 纬度
116.4074, // 经度
300.0f // 半径(米)
)
.setExpirationDuration(Geofence.NEVER_EXPIRE) // 永不过期
.setTransitionTypes(
Geofence.GEOFENCE_TRANSITION_ENTER |
Geofence.GEOFENCE_TRANSITION_EXIT
)
.setLoiteringDelay(60000) // 停留检测延迟(毫秒)
.build();
这里有几个参数需要特别留意:
| 参数 | 说明 | 我的建议 |
|---|---|---|
| setRequestId | 围栏的唯一ID | 用业务含义命名,比如"store_1001" |
| setExpirationDuration | 过期时间(毫秒) | 临时围栏设6小时,长期围栏用NEVER_EXPIRE |
| setLoiteringDelay | 停留检测延迟 | 至少设30秒以上,太短容易误触 |
| setTransitionTypes | 监听的事件类型 | 按需组合,不要全选 |
13.3 GeofencingRequest:注册请求
围栏建好了,怎么告诉系统去监听呢?通过GeofencingRequest。它把多个围栏打包成一个请求,同时指定初始触发行为:
GeofencingRequest request = new GeofencingRequest.Builder()
.addGeofence(geofence) // 可以添加多个围栏
.setInitialTrigger(
GeofencingRequest.INITIAL_TRIGGER_ENTER
)
.build();
这个setInitialTrigger是什么意思?说白了就是:如果用户当前已经在围栏内,注册成功后要不要立刻触发一次进入事件?
- INITIAL_TRIGGER_ENTER:立即触发进入事件(适合「你已经在区域内」的场景)
- INITIAL_TRIGGER_EXIT:立即触发离开事件(很少用)
- INITIAL_TRIGGER_DWELL:立即触发停留事件(需要配合loiteringDelay)
- 不设置:只监听后续变化
我记得有一次做签到功能,用户打开App时如果已经在门店附近,应该自动签到。当时忘了设INITIAL_TRIGGER_ENTER,结果用户站在店里死活触发不了。嗯,这个坑我替你们踩过了。
13.4 广播接收:处理围栏事件
围栏事件是通过广播发送的。你需要创建一个BroadcastReceiver来接收:
public class GeofenceReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
GeofencingEvent event = GeofencingEvent.fromIntent(intent);
if (event == null) return;
int transitionType = event.getGeofenceTransition();
List<Geofence> triggeredFences = event.getTriggeringGeofences();
switch (transitionType) {
case Geofence.GEOFENCE_TRANSITION_ENTER:
// 处理进入事件
break;
case Geofence.GEOFENCE_TRANSITION_EXIT:
// 处理离开事件
break;
case Geofence.GEOFENCE_TRANSITION_DWELL:
// 处理停留事件
break;
}
}
}
别忘了在AndroidManifest里注册:
<receiver android:name=".GeofenceReceiver" />
13.5 完整注册流程
把上面几块串起来,完整的注册代码大概是这样的:
// 1. 检查权限
if (ContextCompat.checkSelfPermission(this,
Manifest.permission.ACCESS_FINE_LOCATION)
!= PackageManager.PERMISSION_GRANTED) {
return;
}
// 2. 构建围栏
Geofence fence = new Geofence.Builder()
.setRequestId("fence_office")
.setCircularRegion(39.9042, 116.4074, 300)
.setExpirationDuration(12 * 60 * 60 * 1000) // 12小时
.setTransitionTypes(Geofence.GEOFENCE_TRANSITION_ENTER)
.build();
// 3. 构建请求
GeofencingRequest request = new GeofencingRequest.Builder()
.addGeofence(fence)
.setInitialTrigger(GeofencingRequest.INITIAL_TRIGGER_ENTER)
.build();
// 4. 注册
GeofencingClient client = LocationServices.getGeofencingClient(this);
PendingIntent pendingIntent = PendingIntent.getBroadcast(
this, 0, new Intent(this, GeofenceReceiver.class),
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE
);
client.addGeofences(request, pendingIntent)
.addOnSuccessListener(aVoid -> {
// 注册成功
})
.addOnFailureListener(e -> {
// 注册失败,检查错误码
});
13.6 知识体系总览
下面这张图帮你理清整个流程:
13.7 避坑指南
最后分享几个我实际项目中遇到的坑:
- 权限问题:Android 10以上,后台位置权限需要单独申请。如果App退到后台还想检测围栏,必须声明
ACCESS_BACKGROUND_LOCATION。 - PendingIntent复用:同一个PendingIntent如果被多个围栏使用,回调时一定要通过
getTriggeringGeofences()区分是哪个围栏触发的。 - 电量消耗:围栏检测本身是低功耗的,但如果你注册太多围栏(超过20个),系统会合并检测间隔,导致响应变慢。
- 测试技巧:在模拟器上测试时,可以用
geo fix命令模拟位置变化。我一般写个自动化脚本,让坐标沿着围栏边界来回移动。
removeGeofences()清理旧的,防止重复注册导致内存泄漏。尤其是Activity重建时,别忘了在onResume里重新注册。
地理围栏这块,说白了就是「画圈+监听」的组合。Builder负责画圈,Request负责打包,Client负责注册,Receiver负责收信。把这四步理清楚,基本就掌握了。剩下的就是根据业务场景灵活组合事件类型和触发条件了。
公众号:蓝海资料掘金营,微信deep3321