Flutter应用后台每30秒向API发送位置更新的实现问题
解决方案:实现Flutter后台持续位置更新
核心问题说明
Dart Isolate本身无法脱离App进程独立运行,当App进入后台后,系统会限制进程资源,导致Isolate被暂停。要实现后台持续运行,必须结合平台原生后台任务机制,同时修正现有代码的资源释放逻辑。
步骤1:修正BackgroundData类代码
以下是修复并增强后的代码,解决了资源泄漏、后台保活触发、位置获取可靠性问题:
import 'dart:async'; import 'dart:isolate'; import 'dart:developer'; import 'package:core/core.dart'; import 'package:maps/map_mixin.dart'; import 'package:geolocator/geolocator.dart'; Duration duration = const Duration(seconds: 30); class BackgroundData with ScaleMapConfigurationsMixin { String? idScale; Isolate? _isolate; ReceivePort? _receivePort; SendPort? _isolateSendPort; final StreamController<String> _streamController = StreamController<String>.broadcast(); StreamSubscription<String>? _subscription; // 添加监听器 void addListener(Function(String) listener) { _subscription?.cancel(); _subscription = _streamController.stream.listen(listener); } // 通知Isolate触发数据发送 void notifyCubitToSendData() { _isolateSendPort?.send('sendData'); } // Isolate入口函数:处理定时任务和位置获取 static void _isolateEntry(List<dynamic> args) async { final mainSendPort = args[0] as SendPort; final taskId = args[1] as String; Timer? timer; // 建立Isolate与主Isolate的通信端口 final isolateReceivePort = ReceivePort(); mainSendPort.send(isolateReceivePort.sendPort); // 请求后台位置权限(Isolate内处理权限检查) await Geolocator.requestPermission(); // 启动定时任务 timer = Timer.periodic(duration, (timer) async { try { // 在Isolate内直接获取位置,避免跨Isolate调用的权限问题 final position = await Geolocator.getCurrentPosition( locationSettings: const LocationSettings( accuracy: LocationAccuracy.best, distanceFilter: 0, ), ); // 发送位置数据到主Isolate,再由Cubit处理API请求 mainSendPort.send({ 'type': 'location', 'data': Coordinates(lat: position.latitude, lng: position.longitude), 'taskId': taskId, }); DateTime now = DateTime.now(); log("BackgroundData - Send location to server ${now.hour}:${now.minute}:${now.second}"); } catch (e) { log("BackgroundData - Failed to get location: $e"); } }); // 监听停止指令 isolateReceivePort.listen((message) { if (message == 'stop') { timer?.cancel(); isolateReceivePort.close(); Isolate.current.kill(priority: Isolate.immediate); } }); } // 启动后台任务 Future<void> startTask(String taskId) async { await SecurePreferences().setString(SecurePreferencesKeys.key_idScaleLocalization, taskId); idScale = taskId; if (_receivePort == null) { _receivePort = ReceivePort(); _isolate = await Isolate.spawn(_isolateEntry, [_receivePort!.sendPort, taskId]); // 处理Isolate发来的消息 _receivePort!.listen((message) { if (message is SendPort) { _isolateSendPort = message; } else if (message is Map) { // 将位置数据转换为通知,触发Cubit的sendData _streamController.add('sendData'); } }); } } // 恢复位置共享 Future<String> continueSharingLocation(String id) async { idScale = await SecurePreferences().getString(SecurePreferencesKeys.key_idScaleLocalization); if (idScale != null && idScale!.isNotEmpty) { await startTask(idScale!); return idScale!; } else { return id; } } // 停止任务并释放所有资源 Future<void> stopTask() async { // 先通知Isolate停止 _isolateSendPort?.send('stop'); // 关闭接收端口 _receivePort?.close(); // 杀死Isolate _isolate?.kill(priority: Isolate.immediate); // 关闭流控制器 await _streamController.close(); // 取消订阅 await _subscription?.cancel(); // 清空本地存储 await SecurePreferences().clearData(SecurePreferencesKeys.key_idScaleLocalization); // 重置变量 idScale = null; _isolate = null; _receivePort = null; _isolateSendPort = null; _subscription = null; } // 主Isolate内获取位置(备用) Future<Coordinates> getLocation() async { await requestPermission(); final position = await geolocatorPosition(); return Coordinates(lat: position.latitude, lng: position.longitude); } }
步骤2:平台级配置(关键!)
仅靠Dart代码无法实现后台保活,必须配置平台权限:
Android 配置
- 在
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.ACCESS_BACKGROUND_LOCATION" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION" />
- 声明前台服务:
在AndroidManifest.xml的<application>标签内添加:
<service android:name=".LocationForegroundService" android:foregroundServiceType="location" />
- 实现原生前台服务:创建
LocationForegroundService类,在启动后台任务时启动前台服务,避免App被系统杀死。
iOS 配置
- 在
Info.plist中添加后台模式权限:
<key>UIBackgroundModes</key> <array> <string>location</string> </array> <key>NSLocationAlwaysAndWhenInUseUsageDescription</key> <string>需要持续获取位置以更新服务</string> <key>NSLocationWhenInUseUsageDescription</key> <string>需要获取位置以更新服务</string> <key>NSLocationAlwaysUsageDescription</key> <string>需要后台持续获取位置以更新服务</string>
- 请求
always位置权限,确保后台能获取位置。
关键改进点
- 资源释放顺序修正:先通知Isolate停止,再关闭端口、杀死Isolate,最后清理变量,避免空指针和资源泄漏。
- Isolate内处理位置获取:直接在后台Isolate中获取位置,减少跨Isolate通信的开销和权限问题。
- 通信端口管理:保存Isolate的SendPort,确保能正确发送停止指令。
- 错误处理:添加位置获取的异常捕获,避免Isolate崩溃。
内容的提问来源于stack exchange,提问作者MarianaSanches
相关产品推荐
相关产品推荐

