Flutter实现全状态(前后台/已关闭)位置监听与Firestore更新方案咨询
Flutter 跨场景位置追踪与Firestore更新实现方案
核心依赖包
先在pubspec.yaml中添加以下依赖:
dependencies: flutter: sdk: flutter geolocator: ^10.1.0 # 位置获取 cloud_firestore: ^4.15.0 # Firestore操作 workmanager: ^0.5.1 # Android后台任务 background_fetch: ^1.2.7 # iOS后台任务
1. 权限配置
Android(AndroidManifest.xml)
添加位置权限和后台任务权限:
<!-- 位置权限 --> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> <!-- 后台位置权限(Android 10+) --> <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" /> <!-- 后台任务权限 --> <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
iOS(Info.plist)
添加位置相关描述:
<key>NSLocationWhenInUseUsageDescription</key> <string>需要获取您的位置以更新位置信息</string> <key>NSLocationAlwaysAndWhenInUseUsageDescription</key> <string>需要在后台持续获取您的位置以更新位置信息</string> <key>UIBackgroundModes</key> <array> <string>location</string> <string>fetch</string> </array>
2. 基础位置处理逻辑
封装位置工具类,包含距离计算、位置监听、Firestore更新:
import 'package:geolocator/geolocator.dart'; import 'package:cloud_firestore/cloud_firestore.dart'; class LocationService { // 存储上一次的位置 Position? _lastPosition; // 距离阈值(100米) static const double distanceThreshold = 100.0; // 初始化权限 Future<void> requestPermissions() async { bool serviceEnabled = await Geolocator.isLocationServiceEnabled(); if (!serviceEnabled) { await Geolocator.openLocationSettings(); return; } LocationPermission permission = await Geolocator.checkPermission(); if (permission == LocationPermission.denied) { permission = await Geolocator.requestPermission(); if (permission == LocationPermission.denied) { return; } } if (permission == LocationPermission.deniedForever) { await Geolocator.openAppSettings(); return; } } // 计算两个位置的距离(米) double calculateDistance(Position a, Position b) { return Geolocator.distanceBetween( a.latitude, a.longitude, b.latitude, b.longitude, ); } // 更新Firestore位置 Future<void> updateUserLocation(String userId, Position position) async { await FirebaseFirestore.instance.collection('users').doc(userId).update({ 'location': GeoPoint(position.latitude, position.longitude), 'updatedAt': FieldValue.serverTimestamp(), }); } // 监听位置变化(前台) void startForegroundLocationListener(String userId) { Geolocator.getPositionStream( locationSettings: const LocationSettings( accuracy: LocationAccuracy.best, distanceFilter: distanceThreshold, // 移动超过100米才触发回调 ), ).listen((Position currentPosition) async { if (_lastPosition == null) { _lastPosition = currentPosition; await updateUserLocation(userId, currentPosition); return; } double distance = calculateDistance(_lastPosition!, currentPosition); if (distance >= distanceThreshold) { _lastPosition = currentPosition; await updateUserLocation(userId, currentPosition); } }); } }
3. 后台任务实现
Android(WorkManager)
初始化WorkManager,设置周期性任务:
import 'package:workmanager/workmanager.dart'; import 'package:shared_preferences/shared_preferences.dart'; void callbackDispatcher() { Workmanager().executeTask((task, inputData) async { final locationService = LocationService(); await locationService.requestPermissions(); Position? currentPosition = await Geolocator.getCurrentPosition( locationSettings: const LocationSettings(accuracy: LocationAccuracy.best), ); if (currentPosition != null) { String userId = inputData?['userId'] ?? ''; final prefs = await SharedPreferences.getInstance(); double? lastLat = prefs.getDouble('lastLat'); double? lastLng = prefs.getDouble('lastLng'); if (lastLat == null || lastLng == null) { await locationService.updateUserLocation(userId, currentPosition); await prefs.setDouble('lastLat', currentPosition.latitude); await prefs.setDouble('lastLng', currentPosition.longitude); return true; } Position lastPosition = Position( latitude: lastLat, longitude: lastLng, timestamp: DateTime.now(), accuracy: 0, altitude: 0, heading: 0, speed: 0, speedAccuracy: 0, ); double distance = locationService.calculateDistance(lastPosition, currentPosition); if (distance >= LocationService.distanceThreshold) { await locationService.updateUserLocation(userId, currentPosition); await prefs.setDouble('lastLat', currentPosition.latitude); await prefs.setDouble('lastLng', currentPosition.longitude); } } return true; }); } // 初始化WorkManager void initWorkManager(String userId) { Workmanager().initialize( callbackDispatcher, isInDebugMode: false, ); // 设置周期性任务,每15分钟执行一次(Android最小周期限制) Workmanager().registerPeriodicTask( 'location-tracking-task', 'location-task', inputData: {'userId': userId}, frequency: const Duration(minutes: 15), initialDelay: const Duration(minutes: 1), constraints: Constraints( networkType: NetworkType.connected, // 可选:仅在联网时执行 requiresBatteryNotLow: false, ), ); }
iOS(Background Fetch)
初始化Background Fetch,设置任务:
import 'package:background_fetch/background_fetch.dart'; import 'package:shared_preferences/shared_preferences.dart'; void backgroundFetchHeadlessTask(HeadlessTask task) async { String taskId = task.taskId; bool isTimeout = task.timeout; if (isTimeout) { BackgroundFetch.finish(taskId); return; } final locationService = LocationService(); await locationService.requestPermissions(); Position? currentPosition = await Geolocator.getCurrentPosition( locationSettings: const LocationSettings(accuracy: LocationAccuracy.best), ); if (currentPosition != null) { final prefs = await SharedPreferences.getInstance(); String userId = prefs.getString('userId') ?? ''; double? lastLat = prefs.getDouble('lastLat'); double? lastLng = prefs.getDouble('lastLng'); if (lastLat == null || lastLng == null) { await locationService.updateUserLocation(userId, currentPosition); await prefs.setDouble('lastLat', currentPosition.latitude); await prefs.setDouble('lastLng', currentPosition.longitude); } else { Position lastPosition = Position( latitude: lastLat, longitude: lastLng, timestamp: DateTime.now(), accuracy: 0, altitude: 0, heading: 0, speed: 0, speedAccuracy: 0, ); double distance = locationService.calculateDistance(lastPosition, currentPosition); if (distance >= LocationService.distanceThreshold) { await locationService.updateUserLocation(userId, currentPosition); await prefs.setDouble('lastLat', currentPosition.latitude); await prefs.setDouble('lastLng', currentPosition.longitude); } } } BackgroundFetch.finish(taskId); } // 初始化Background Fetch void initBackgroundFetch(String userId) async { final prefs = await SharedPreferences.getInstance(); await prefs.setString('userId', userId); await BackgroundFetch.configure( BackgroundFetchConfig( minimumFetchInterval: 15, // iOS最小间隔15分钟 stopOnTerminate: false, // 应用关闭后仍执行 startOnBoot: true, // 设备重启后启动 enableHeadless: true, // 启用无头任务 ), (String taskId) async { // 前台后台回调 await backgroundFetchHeadlessTask(HeadlessTask(taskId, false)); }, (String taskId) async { // 超时回调 BackgroundFetch.finish(taskId); }, ); // 注册无头任务(应用关闭时触发) BackgroundFetch.registerHeadlessTask(backgroundFetchHeadlessTask); }
4. 整合到应用入口
在main.dart中初始化所有服务:
import 'package:flutter/material.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:firebase_core/firebase_core.dart'; void main() async { WidgetsFlutterBinding.ensureInitialized(); // 初始化Firebase(需先配置Firebase项目) await Firebase.initializeApp(); final prefs = await SharedPreferences.getInstance(); // 替换为实际用户ID获取逻辑,比如从登录状态读取 String userId = prefs.getString('userId') ?? 'test-user-123'; final locationService = LocationService(); await locationService.requestPermissions(); // 初始化后台任务 initWorkManager(userId); initBackgroundFetch(userId); // 启动前台监听 locationService.startForegroundLocationListener(userId); runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( appBar: AppBar(title: const Text('位置追踪')), body: const Center(child: Text('位置追踪服务已启动')), ), ); } }
关键注意事项
- 权限问题:Android 10+必须申请
ACCESS_BACKGROUND_LOCATION权限,iOS需要开启Always位置权限,否则后台无法获取位置。 - 电池优化:部分Android设备会限制后台任务,需引导用户关闭应用的电池优化。
- 本地存储:必须将上一次位置和用户ID存在本地(如
SharedPreferences),后台任务无法直接访问应用内存数据。 - Firestore写入限制:通过距离阈值过滤减少写入频率,降低费用和带宽消耗。
- iOS后台限制:iOS的Background Fetch间隔由系统调度,无法做到像Android那样的固定周期,活跃度低的应用可能被系统降低调度频率。
内容的提问来源于stack exchange,提问作者pfe ebnou
相关产品推荐
相关产品推荐

