14、围栏事件:GeofenceEvent解析、进入/退出/驻留判断、围栏半径与过期时间设置
各位同学,今天我们来聊聊围栏事件。说实话,Geofencing 这个功能,我第一次接触时觉得挺神奇的——手机明明没动,怎么就知道我进了一个区域?后来搞明白了,其实它背后就是一套位置比对逻辑。但真正让我头疼的,是事件判断的细节。
我记得在做一个商场导购项目时,用户刚走到商场门口,围栏就触发了。但用户只是路过,根本没进去。结果推送了一堆优惠券,用户直接卸载了App。嗯,这就是我们今天要重点解决的问题。
14.1 GeofenceEvent 解析
GeofenceEvent 是 Google Play Services 提供的一个事件对象。它封装了围栏触发的所有信息。说白了,它就是系统告诉你:「嘿,你的围栏有动静了」。
先看一个典型的事件结构:
public class GeofenceEvent {
// 触发类型:进入、退出、驻留
private int geofenceTransition;
// 触发围栏的列表
private List<Geofence> triggeringGeofences;
// 触发时的位置
private Location triggeringLocation;
// 错误码
private int errorCode;
// 围栏请求ID列表
private List<String> triggeringGeofenceIds;
}
我个人习惯在 onReceive 方法里先做一次校验:
@Override
public void onReceive(Context context, Intent intent) {
GeofenceEvent geofenceEvent = GeofenceEvent.fromIntent(intent);
// 先检查有没有错误
if (geofenceEvent.hasError()) {
int errorCode = geofenceEvent.getErrorCode();
Log.e(TAG, "围栏事件错误: " + errorCode);
return;
}
// 再获取触发类型
int transition = geofenceEvent.getGeofenceTransition();
Log.d(TAG, "触发类型: " + transition);
}
注意:GeofenceEvent 的 fromIntent 方法可能返回 null。我在项目中遇到过这种情况,原因是 Intent 的 action 不对。一定要检查 null,否则直接空指针崩溃。
14.2 进入/退出/驻留判断
三种事件类型,对应三个常量:
| 常量 | 值 | 说明 |
|---|---|---|
| Geofence.GEOFENCE_TRANSITION_ENTER | 1 | 进入围栏 |
| Geofence.GEOFENCE_TRANSITION_EXIT | 2 | 退出围栏 |
| Geofence.GEOFENCE_TRANSITION_DWELL | 4 | 驻留在围栏内 |
判断逻辑其实很简单:
switch (transition) {
case Geofence.GEOFENCE_TRANSITION_ENTER:
// 用户进入了围栏区域
handleEnterEvent(geofenceEvent);
break;
case Geofence.GEOFENCE_TRANSITION_EXIT:
// 用户离开了围栏区域
handleExitEvent(geofenceEvent);
break;
case Geofence.GEOFENCE_TRANSITION_DWELL:
// 用户在围栏内停留了一段时间
handleDwellEvent(geofenceEvent);
break;
}
你可能会问:「驻留和进入有什么区别?」
举个例子。你走进一家咖啡店,这是「进入」。你在店里坐了20分钟,这是「驻留」。如果只是路过,系统只会触发进入,然后很快触发退出。驻留事件需要你设置一个时间阈值,比如至少待5分钟才算驻留。
我的经验:驻留事件非常耗电。因为系统需要持续监测你的位置。我一般只在关键场景使用,比如用户到达停车场后需要自动记录停车位置。普通场景用进入和退出就够了。
14.3 围栏半径与过期时间设置
这两个参数,直接影响围栏的准确性和生命周期。
14.3.1 半径设置
半径的单位是米。官方建议最小100米。为什么?因为GPS的精度一般在10-50米。你设个10米的围栏,系统根本判断不准。
我曾经做过一个项目,客户要求围栏半径50米。结果测试时发现,用户站在围栏中心,系统有时触发进入,有时不触发。后来我把半径改到150米,问题就解决了。
设置方式:
Geofence geofence = new Geofence.Builder()
.setRequestId("my_geofence")
.setCircularRegion(latitude, longitude, 100) // 半径100米
.setTransitionTypes(Geofence.GEOFENCE_TRANSITION_ENTER |
Geofence.GEOFENCE_TRANSITION_EXIT)
.setExpirationDuration(Geofence.NEVER_EXPIRE)
.build();
核心建议:
- 城市环境:建议100-200米
- 郊区环境:建议200-500米
- 室内场景:建议50-100米(配合WiFi定位)
14.3.2 过期时间设置
过期时间有两种设置方式:
- 绝对过期时间:指定一个具体的时间点
- 相对过期时间:从注册开始算,多少毫秒后过期
// 方式一:永不过期
.setExpirationDuration(Geofence.NEVER_EXPIRE)
// 方式二:24小时后过期
.setExpirationDuration(24 * 60 * 60 * 1000)
// 方式三:指定具体时间(不推荐,因为系统时间可能不准)
.setExpirationTime(System.currentTimeMillis() + 86400000)
我个人习惯用相对过期时间。为什么?因为绝对时间依赖系统时钟。用户改了系统时间,你的围栏可能提前过期或者延迟过期。
避坑指南:千万不要把所有围栏都设成NEVER_EXPIRE。系统对围栏数量有限制(每个App最多100个)。如果围栏不清理,注册满了新围栏就加不进去了。我建议根据业务场景设置合理的过期时间,比如活动围栏活动结束后就过期。
14.4 知识体系图
下面这张图,帮你理清围栏事件的核心脉络:
14.5 完整示例代码
最后,给一个完整的围栏事件处理示例。这是我项目中常用的模板:
public class GeofenceBroadcastReceiver extends BroadcastReceiver {
private static final String TAG = "GeofenceReceiver";
@Override
public void onReceive(Context context, Intent intent) {
GeofenceEvent geofenceEvent = GeofenceEvent.fromIntent(intent);
if (geofenceEvent == null) {
Log.w(TAG, "GeofenceEvent 为 null");
return;
}
if (geofenceEvent.hasError()) {
handleError(geofenceEvent.getErrorCode());
return;
}
int transition = geofenceEvent.getGeofenceTransition();
List<String> geofenceIds = geofenceEvent.getTriggeringGeofenceIds();
switch (transition) {
case Geofence.GEOFENCE_TRANSITION_ENTER:
Log.d(TAG, "进入围栏: " + geofenceIds);
// 发送通知
sendNotification(context, "欢迎来到我们的区域");
break;
case Geofence.GEOFENCE_TRANSITION_EXIT:
Log.d(TAG, "退出围栏: " + geofenceIds);
// 清理状态
clearState(geofenceIds);
break;
case Geofence.GEOFENCE_TRANSITION_DWELL:
Log.d(TAG, "驻留围栏: " + geofenceIds);
// 用户停留了足够长时间
triggerDwellAction(context, geofenceIds);
break;
}
}
private void handleError(int errorCode) {
switch (errorCode) {
case GeofenceStatusCodes.GEOFENCE_NOT_AVAILABLE:
Log.e(TAG, "围栏服务不可用");
break;
case GeofenceStatusCodes.GEOFENCE_TOO_MANY_GEOFENCES:
Log.e(TAG, "围栏数量超限");
break;
case GeofenceStatusCodes.GEOFENCE_TOO_MANY_PENDING_INTENTS:
Log.e(TAG, "PendingIntent 数量超限");
break;
}
}
}
小技巧:调试围栏事件时,可以用 Android Studio 的 Location 模拟器。手动设置一个经纬度,然后观察围栏是否触发。比真机测试方便多了。
好了,围栏事件的核心内容就这些。记住三点:事件类型要判断准确,半径设置要合理,过期时间要规划好。做到这三点,你的围栏功能基本不会出大问题。
公众号:蓝海资料掘金营,微信deep3321