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

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 配置

  1. 在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" />
  1. 声明前台服务:
    在AndroidManifest.xml的<application>标签内添加:
<service
    android:name=".LocationForegroundService"
    android:foregroundServiceType="location" />
  1. 实现原生前台服务:创建LocationForegroundService类,在启动后台任务时启动前台服务,避免App被系统杀死。

iOS 配置

  1. 在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>
  1. 请求always位置权限,确保后台能获取位置。

关键改进点

  1. 资源释放顺序修正:先通知Isolate停止,再关闭端口、杀死Isolate,最后清理变量,避免空指针和资源泄漏。
  2. Isolate内处理位置获取:直接在后台Isolate中获取位置,减少跨Isolate通信的开销和权限问题。
  3. 通信端口管理:保存Isolate的SendPort,确保能正确发送停止指令。
  4. 错误处理:添加位置获取的异常捕获,避免Isolate崩溃。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.19 04:19:55