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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.14 22:47:15