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 知识体系图

下面这张图,帮你理清围栏事件的核心脉络:

围栏事件核心知识体系 GeofenceEvent 事件类型判断 半径与过期时间 业务逻辑处理 进入(ENTER) 退出(EXIT) 驻留(DWELL) 半径设置 过期时间 触发类型组合 推送通知 记录日志 触发回调 核心原则:先校验 → 再判断类型 → 最后处理业务 半径不宜过小,过期时间要合理,驻留事件慎用

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