Flutter如何实现点击触发后定时间隔向服务器上报位置
Flutter 前后台固定间隔持续位置上报实现方案
你当前的单次上报逻辑仅能触发一次执行,普通循环方案在应用退后台时会被系统挂起,且无法灵活管控启停,容易造成内存泄漏和异常耗电,正确实现需要覆盖位置持续监听、可管控定时任务、前后台保活三个核心部分,具体实现如下:
1. 核心实现逻辑说明
- 采用单例模式管理上报任务,避免重复创建实例导致多个上报任务冲突
- 用位置流持续监听替代单次主动拉取定位,性能开销更低,位置更新更及时
- 用可手动取消的周期性定时器触发上报,支持随时启停,避免任务泄漏
- 开启系统级后台定位/前台服务能力,保证应用退后台后任务不会被系统冻结
2. 原有代码改造
改造位置管理类 TrackingManager
新增权限检查、持续位置监听、定时任务管控、启停能力:
import 'dart:async'; import 'package:location/location.dart'; class TrackingManager { static final TrackingManager _instance = TrackingManager._internal(); factory TrackingManager() => _instance; TrackingManager._internal(); final Location _locate = Location(); Timer? _reportTimer; StreamSubscription<LocationData>? _locationSub; // 缓存最新位置,定时器触发时直接读取,无需每次等待定位返回 Map<String, double> _latestLocation = {'latitude': 0, 'longitude': 0}; bool _isTracking = false; // 定位权限、服务可用性检查 Future<bool> _checkPermission() async { bool serviceEnabled = await _locate.serviceEnabled(); if (!serviceEnabled) { serviceEnabled = await _locate.requestService(); if (!serviceEnabled) return false; } PermissionStatus permissionGranted = await _locate.hasPermission(); if (permissionGranted == PermissionStatus.denied) { permissionGranted = await _locate.requestPermission(); if (permissionGranted != PermissionStatus.granted) return false; } // 开启后台定位模式,保证退后台仍能获取位置 await _locate.enableBackgroundMode(enable: true); return true; } /// 启动持续位置上报 /// [reportIntervalSeconds] 上报间隔,单位秒 /// [accessToken] 接口鉴权token Future<void> startTracking({ required int reportIntervalSeconds, required String accessToken, }) async { if (_isTracking) return; bool permissionPass = await _checkPermission(); if (!permissionPass) throw Exception('定位权限未开启'); _isTracking = true; // 持续监听位置更新,缓存最新坐标 _locationSub = _locate.onLocationChanged.listen((LocationData data) { _latestLocation['latitude'] = data.latitude ?? 0; _latestLocation['longitude'] = data.longitude ?? 0; }); // 启动定时上报任务 _reportTimer = Timer.periodic(Duration(seconds: reportIntervalSeconds), (timer) async { try { await LocateApiRequest().transmit(accessToken, _latestLocation); } catch (e) { // 上报失败可自行添加重试、日志记录逻辑,不要让异常中断定时器 print('位置上报失败: $e'); } }); } /// 停止上报,退出页面、用户点击停止、退出登录时必须调用 void stopTracking() { _reportTimer?.cancel(); _locationSub?.cancel(); _reportTimer = null; _locationSub = null; _isTracking = false; _locate.enableBackgroundMode(enable: false); } }
改造上报请求类 LocateApiRequest
解耦位置获取逻辑,位置数据从参数传入,避免类之间强耦合:
import 'dart:convert'; class LocateApiRequest { final _networkManager = NetworkManager(); Future transmit(String accessToken, Map<String, double> locationdata) async { final response = await _networkManager .send(ApiRequestFactory.transmit(locationdata, accessToken)); final statusCode = response.statusCode; if (statusCode == 200) { final responseBody = jsonDecode(response.body); return responseBody; } throw statusCode; } }
3. 前后台保活配置(必须配置,否则退后台任务会被系统挂起)
Android端配置
- 在
AndroidManifest.xml中添加所需权限:
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <uses-permission android:name="android.permission.WAKE_LOCK" />- 通过
WidgetsBindingObserver监听应用生命周期,退到后台时启动前台服务,显示常驻通知栏(Android系统强制要求后台定位必须有可见通知,否则会杀死进程),回到前台后关闭前台服务即可。
- 在
iOS端配置
- 在
Info.plist中添加后台定位权限声明和后台模式配置:
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key> <string>需要持续获取位置用于位置上报</string> <key>UIBackgroundModes</key> <array> <string>location</string> </array>- 申请定位权限时需申请「始终允许」权限,不能仅申请前台使用权限。
- 在
4. 调用方式
用户点击开始上报按钮时调用启动方法:
TrackingManager().startTracking( reportIntervalSeconds: 10, // 按业务需求设置上报间隔,示例为10秒一次 accessToken: '当前用户的鉴权token', );
用户点击停止按钮、退出登录、离开上报页面时必须调用停止方法,避免异常耗电:
TrackingManager().stopTracking();
注意:不要使用
while(true)+Future.delayed的循环方式实现定时,该写法在事件队列拥堵时会出现间隔严重不准的问题,且一旦循环内未捕获异常会直接导致整个上报任务中断,退后台后也会被系统直接冻结执行,稳定性无法保障。
内容的提问来源于stack exchange,提问作者idkanything
相关产品推荐
相关产品推荐

