You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android Geofence后台服务配置:解决APP关闭后地理围栏失效问题

解决APP关闭后地理围栏失效的问题

你的问题核心在于地理围栏的监听逻辑绑定在了Activity的生命周期上,当APP关闭或退到后台时,Activity被销毁,对应的GeoQuery监听和位置更新也会停止。要解决这个问题,需要把地理围栏和位置监控的逻辑移到**前台服务(Foreground Service)**中——前台服务拥有更高的系统优先级,不容易被系统回收,能在后台持续运行。

下面是具体的实现步骤:

1. 添加必要的权限

在AndroidManifest.xml中添加前台服务和后台位置权限,同时注册你的服务:

<!-- 基础位置权限 -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />

<!-- 前台服务权限 -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION" />

<!-- Android 10+ 后台位置权限 -->
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />

<!-- 注册服务 -->
<service android:name=".GeoFenceMonitoringService" />

2. 创建前台服务类

新建GeoFenceMonitoringService类,继承自Service,在这里托管GeoFire监听、位置更新逻辑,并且启动前台通知:

public class GeoFenceMonitoringService extends Service implements GoogleApiClient.ConnectionCallbacks,
        GoogleApiClient.OnConnectionFailedListener, LocationListener {

    private GoogleApiClient mGoogleApiClient;
    private LocationRequest mLocationRequest;
    private GeoFire geoFire;
    private DatabaseReference ref;
    private GeoQuery geoQuery;
    private static final int MONITORING_NOTIFICATION_ID = 1001;

    @Override
    public void onCreate() {
        super.onCreate();
        // 初始化Firebase和GeoFire
        ref = FirebaseDatabase.getInstance().getReference("MyLocation");
        geoFire = new GeoFire(ref);
        // 初始化GoogleApiClient
        buildGoogleApiClient();
        // 启动前台服务,显示常驻通知
        startForeground(MONITORING_NOTIFICATION_ID, createMonitoringNotification());
    }

    // 创建前台服务通知
    private Notification createMonitoringNotification() {
        NotificationCompat.Builder builder = new NotificationCompat.Builder(this, "LOCATION_MONITORING_CHANNEL")
                .setContentTitle("位置监控中")
                .setContentText("正在监控地理围栏区域")
                .setSmallIcon(R.drawable.ic_notification)
                .setPriority(NotificationCompat.PRIORITY_LOW);

        // Android 8.0+ 创建通知渠道
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            NotificationChannel channel = new NotificationChannel(
                    "LOCATION_MONITORING_CHANNEL",
                    "位置监控",
                    NotificationManager.IMPORTANCE_LOW
            );
            NotificationManager manager = getSystemService(NotificationManager.class);
            manager.createNotificationChannel(channel);
        }
        return builder.build();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        if (mGoogleApiClient != null && !mGoogleApiClient.isConnected()) {
            mGoogleApiClient.connect();
        }
        // 设置地理围栏监听
        setupGeoFence();
        return START_STICKY; // 服务被系统杀死后尝试自动重启
    }

    // 初始化地理围栏监听
    private void setupGeoFence() {
        LatLng dangerous_area = new LatLng(23.7424236, 90.3942189);
        // 创建半径0.1公里(100米)的地理查询
        geoQuery = geoFire.queryAtLocation(
                new GeoLocation(dangerous_area.latitude, dangerous_area.longitude),
                0.1f
        );

        geoQuery.addGeoQueryEventListener(new GeoQueryEventListener() {
            @Override
            public void onKeyEntered(String key, GeoLocation location) {
                sendAlertNotification("MRF", String.format("%s进入危险区域", key));
            }

            @Override
            public void onKeyExited(String key) {
                sendAlertNotification("MRF", String.format("%s离开危险区域", key));
            }

            @Override
            public void onKeyMoved(String key, GeoLocation location) {
                Log.d("MOVE", String.format("%s在危险区域内移动:[%f/%f]", key, location.getLatitude(), location.getLongitude()));
            }

            @Override
            public void onGeoQueryReady() {}
            @Override
            public void onGeoQueryError(DatabaseError error) {}
        });
    }

    // 发送警报通知
    private void sendAlertNotification(String title, String message) {
        NotificationCompat.Builder builder = new NotificationCompat.Builder(this, "LOCATION_ALERT_CHANNEL")
                .setContentTitle(title)
                .setContentText(message)
                .setSmallIcon(R.drawable.ic_alert)
                .setPriority(NotificationCompat.PRIORITY_HIGH)
                .setAutoCancel(true);

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            NotificationChannel channel = new NotificationChannel(
                    "LOCATION_ALERT_CHANNEL",
                    "位置警报",
                    NotificationManager.IMPORTANCE_HIGH
            );
            NotificationManager manager = getSystemService(NotificationManager.class);
            manager.createNotificationChannel(channel);
        }
        NotificationManagerCompat.from(this).notify(new Random().nextInt(), builder.build());
    }

    // 以下是位置更新相关逻辑
    private void createLocationRequest() {
        mLocationRequest = new LocationRequest();
        mLocationRequest.setInterval(10000); // 10秒更新一次位置
        mLocationRequest.setFastestInterval(5000); // 最快5秒更新
        mLocationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
        mLocationRequest.setSmallestDisplacement(10); // 移动10米才触发更新
    }

    private void buildGoogleApiClient() {
        mGoogleApiClient = new GoogleApiClient.Builder(this)
                .addConnectionCallbacks(this)
                .addOnConnectionFailedListener(this)
                .addApi(LocationServices.API)
                .build();
    }

    private void startLocationUpdates() {
        if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED
                && ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
            return;
        }
        LocationServices.FusedLocationApi.requestLocationUpdates(mGoogleApiClient, mLocationRequest, this);
    }

    @Override
    public void onConnected(@Nullable Bundle bundle) {
        createLocationRequest();
        startLocationUpdates();
        updateCurrentLocation();
    }

    // 更新当前位置到Firebase
    private void updateCurrentLocation() {
        if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED
                && ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
            return;
        }
        Location lastLocation = LocationServices.FusedLocationApi.getLastLocation(mGoogleApiClient);
        if (lastLocation != null) {
            geoFire.setLocation("You", new GeoLocation(lastLocation.getLatitude(), lastLocation.getLongitude()), (key, error) -> {
                if (error != null) {
                    Log.e("GeoFire", "位置上传失败:" + error.getMessage());
                }
            });
        }
    }

    @Override
    public void onLocationChanged(Location location) {
        // 位置变化时更新到Firebase
        geoFire.setLocation("You", new GeoLocation(location.getLatitude(), location.getLongitude()), (key, error) -> {
            if (error != null) {
                Log.e("GeoFire", "位置更新失败:" + error.getMessage());
            }
        });
    }

    @Override
    public void onConnectionSuspended(int i) {
        mGoogleApiClient.connect();
    }

    @Override
    public void onConnectionFailed(@NonNull ConnectionResult connectionResult) {
        Log.e("GoogleApi", "连接失败:" + connectionResult.getErrorMessage());
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null; // 不需要绑定Activity,返回null
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        // 释放资源,避免内存泄漏
        if (mGoogleApiClient != null && mGoogleApiClient.isConnected()) {
            LocationServices.FusedLocationApi.removeLocationUpdates(mGoogleApiClient, this);
            mGoogleApiClient.disconnect();
        }
        if (geoQuery != null) {
            geoQuery.removeAllListeners();
        }
    }
}

3. 修改MapsActivity,启动前台服务

把原来Activity中的位置更新和地理围栏逻辑移除,改为启动服务:

public class MapsActivity extends FragmentActivity implements OnMapReadyCallback {

    private GoogleMap mMap;
    private static final int PERMISSION_REQUEST_CODE = 100;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_maps);
        SupportMapFragment mapFragment = (SupportMapFragment) getSupportFragmentManager()
                .findFragmentById(R.id.map);
        mapFragment.getMapAsync(this);
        // 检查权限并启动监控服务
        checkPermissionsAndStartService();
    }

    private void checkPermissionsAndStartService() {
        List<String> missingPermissions = new ArrayList<>();

        if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
            missingPermissions.add(Manifest.permission.ACCESS_FINE_LOCATION);
        }
        if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
            missingPermissions.add(Manifest.permission.ACCESS_COARSE_LOCATION);
        }
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_BACKGROUND_LOCATION) != PackageManager.PERMISSION_GRANTED) {
            missingPermissions.add(Manifest.permission.ACCESS_BACKGROUND_LOCATION);
        }

        if (!missingPermissions.isEmpty()) {
            ActivityCompat.requestPermissions(this, missingPermissions.toArray(new String[0]), PERMISSION_REQUEST_CODE);
        } else {
            startMonitoringService();
        }
    }

    private void startMonitoringService() {
        Intent serviceIntent = new Intent(this, GeoFenceMonitoringService.class);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            startForegroundService(serviceIntent);
        } else {
            startService(serviceIntent);
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        if (requestCode == PERMISSION_REQUEST_CODE) {
            boolean allGranted = true;
            for (int result : grantResults) {
                if (result != PackageManager.PERMISSION_GRANTED) {
                    allGranted = false;
                    break;
                }
            }
            if (allGranted) {
                startMonitoringService();
            } else {
                Toast.makeText(this, "需要位置权限才能监控地理围栏", Toast.LENGTH_SHORT).show();
            }
        }
    }

    @Override
    public void onMapReady(GoogleMap googleMap) {
        mMap = googleMap;
        // 仅在地图上显示危险区域标记
        LatLng dangerous_area = new LatLng(23.7424236, 90.3942189);
        mMap.addCircle(new CircleOptions()
                .center(dangerous_area)
                .radius(100)
                .strokeColor(Color.BLUE)
                .fillColor(0x220000FF)
                .strokeWidth(5.0f));
    }
}

关键注意事项

  • 前台服务必须显示通知:Android 8.0及以上,前台服务必须绑定一个常驻通知,否则会被系统终止。
  • 后台位置权限:Android 10+ 要在后台持续获取位置,必须申请ACCESS_BACKGROUND_LOCATION权限,申请时要向用户说明用途。
  • 厂商后台限制:部分国产手机厂商有严格的后台管理策略,需要引导用户开启应用的“自启动权限”和“后台活动权限”,避免服务被强制杀死。
  • 资源释放:在服务的onDestroy方法中必须移除所有监听,避免内存泄漏。

内容的提问来源于stack exchange,提问作者Mahmudur Rahman

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.27 07:36:04