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

Flutter实现全局后台定位追踪(满足3秒间隔需求)

Flutter全局后台高频定位实现方案

问题描述

需要在应用所有页面后台追踪用户位置,目前用geolocator包实现了位置获取逻辑,但每个页面都要重复编写代码。尝试过workmanager,但它最小执行间隔为15分钟,无法满足3秒一次的高频定位需求。

当前实现代码:

getPosicaoAtual() async {
    try {
      Position posicao = await _posicaoAtual();
      DbUtil.insert('local', {
        'latitude': posicao.latitude,
        'longitude': posicao.longitude,
      });
      _local = {
        'latitude': posicao.latitude,
        'longitude': posicao.longitude,
      };
    } catch (e) {
      clearLocal();
      _local['erro'] = e.toString();
    }
    notifyListeners();
  }

  Future<Position> _posicaoAtual() async {
    LocationPermission permissao;
    bool ativado = await Geolocator.isLocationServiceEnabled();
    if (!ativado) {
      if (!await Geolocator.isLocationServiceEnabled()) {
        return Future.error('Por favor, habilite a localização no smartphone');
      }
    }

    permissao = await Geolocator.checkPermission();
    if (permissao == LocationPermission.denied) {
      permissao = await Geolocator.requestPermission();
      if (permissao == LocationPermission.denied) {
        return Future.error('Você precisa autorizar o acesso à localização');
      }
    }

    if (permissao == LocationPermission.deniedForever) {
      return Future.error('-1');
    }
    return await Geolocator.getCurrentPosition();
  }

解决方案

1. 封装全局单例位置服务类

创建一个单例类,把权限校验、位置监听、数据持久化逻辑全部封装进去,所有页面只需订阅这个类的位置流即可,无需重复编写代码。

示例代码:

import 'package:geolocator/geolocator.dart';
import 'dart:async';

class LocationService {
  static final LocationService _instance = LocationService._internal();
  factory LocationService() => _instance;
  LocationService._internal();

  StreamSubscription<Position>? _locationSubscription;
  final StreamController<Map<String, dynamic>> _locationStreamController = StreamController.broadcast();
  Stream<Map<String, dynamic>> get locationStream => _locationStreamController.stream;

  Future<void> startTracking() async {
    // 复用原有权限与服务检查逻辑
    await _checkPermissionsAndService();

    // 启动3秒间隔的位置监听
    _locationSubscription = Geolocator.getPositionStream(
      locationSettings: const LocationSettings(
        accuracy: LocationAccuracy.high,
        distanceFilter: 0, // 忽略移动距离,严格按时间间隔更新
        intervalDuration: Duration(seconds: 3),
      ),
    ).listen((Position position) {
      // 插入数据库
      DbUtil.insert('local', {
        'latitude': position.latitude,
        'longitude': position.longitude,
      });
      // 推送位置数据到全局流
      _locationStreamController.add({
        'latitude': position.latitude,
        'longitude': position.longitude,
        'erro': null,
      });
    }, onError: (error) {
      // 推送错误信息
      _locationStreamController.add({
        'latitude': null,
        'longitude': null,
        'erro': error.toString(),
      });
    });
  }

  Future<void> stopTracking() async {
    await _locationSubscription?.cancel();
    _locationSubscription = null;
  }

  Future<void> _checkPermissionsAndService() async {
    bool serviceEnabled = await Geolocator.isLocationServiceEnabled();
    if (!serviceEnabled) {
      throw Exception('请在手机设置中启用定位服务');
    }

    LocationPermission permission = await Geolocator.checkPermission();
    if (permission == LocationPermission.denied) {
      permission = await Geolocator.requestPermission();
      if (permission == LocationPermission.denied) {
        throw Exception('你需要授权位置访问权限');
      }
    }

    if (permission == LocationPermission.deniedForever) {
      throw Exception('-1');
    }
  }
}

2. 全局初始化服务

在应用启动时(比如main函数或根Widget的initState)启动定位服务,确保整个应用生命周期内都能持续追踪:

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  try {
    await LocationService().startTracking();
  } catch (e) {
    print(e);
  }
  runApp(const MyApp());
}

3. 页面订阅位置流

任意页面只需订阅全局位置流,就能实时获取位置更新,无需重复写定位逻辑:

class AnyPage extends StatefulWidget {
  const AnyPage({super.key});

  @override
  State<AnyPage> createState() => _AnyPageState();
}

class _AnyPageState extends State<AnyPage> {
  StreamSubscription? _locationSubscription;
  Map<String, dynamic>? _currentLocation;

  @override
  void initState() {
    super.initState();
    // 订阅全局位置流
    _locationSubscription = LocationService().locationStream.listen((location) {
      setState(() {
        _currentLocation = location;
      });
    });
  }

  @override
  void dispose() {
    _locationSubscription?.cancel();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: _currentLocation == null
            ? const Text('正在获取位置...')
            : _currentLocation!['erro'] != null
                ? Text('错误: ${_currentLocation!['erro']}')
                : Text('纬度: ${_currentLocation!['latitude']}, 经度: ${_currentLocation!['longitude']}'),
      ),
    );
  }
}

4. 后台运行保障(核心)

若需应用退到后台后仍保持3秒间隔定位,必须配置前台服务,否则系统会因资源限制终止高频定位任务:

  • Android端:在AndroidManifest.xml中添加权限:

    <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION" />
    

    可配合flutter_foreground_task包实现前台服务,避免应用被系统杀死。

  • iOS端:在Info.plist中配置后台模式:

    <key>UIBackgroundModes</key>
    <array>
        <string>location</string>
    </array>
    

    同时请求always类型的位置权限(而非whenInUse),确保后台定位权限生效。

注意事项

  • 3秒间隔的高频定位会显著增加电量消耗,需结合产品场景评估必要性;
  • iOS对后台定位的审核严格,需确保应用场景符合Apple的后台定位政策(如导航、物流追踪类);
  • 务必在真机上测试,模拟器的后台行为与真机存在差异。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.07 18:15:42