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
相关产品推荐
相关产品推荐

