Skip to content

flutter-background-boundary ​

1. 实验目标与工程要点 ​

用最小页面验证 Flutter 的后台执行边界:Dart 代码不能给自己争取后台执行窗口。

  • WidgetsBindingObserver.didChangeAppLifecycleState 记录 inactive / hidden / paused / resumed(detached 部分平台才会出现)
  • 用 Timer.periodic(1s) 模拟「后台同步任务」,观察 App 进 paused 后 tick 是否还在触发
  • 回到 resumed 时用 墙钟秒数 vs 实际 tick 次数 的差值,量化后台期间被系统挂起吞掉的时间

工程要点:

  • observer 必须 initState 注册、dispose 注销,成对出现
  • 「Dart 任务还活着」≠「还在执行」:paused 后定时器被冻结,isolate 不改变这一点
  • 真正可靠的后台要落到原生能力(WorkManager / FGS / BGTask),Flutter 只做调用与展示层

2. 关键实现速览 ​

2.1 生命周期监听 + 事件日志 ​

dart
class _FlutterBackgroundBoundaryDemoPageState
    extends State<FlutterBackgroundBoundaryDemoPage>
    with WidgetsBindingObserver {
  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addObserver(this);
    _log('initState:已注册 WidgetsBindingObserver');
  }

  @override
  void didChangeAppLifecycleState(AppLifecycleState state) {
    switch (state) {
      case AppLifecycleState.paused:
        _log('AppLifecycleState.paused:进后台 → Dart 定时器即将被冻结');
        break;
      case AppLifecycleState.resumed:
        _reportMissedTicks();
        break;
      // inactive / hidden / detached 同样记录 ...
    }
  }

  @override
  void dispose() {
    WidgetsBinding.instance.removeObserver(this);
    super.dispose();
  }
}

为什么看这段:它把宿主 App 的前后台信号变成可读日志,是判断「页面没销毁但任务停了」的第一手证据。

2.2 模拟后台任务与挂起量化 ​

dart
_ticker = Timer.periodic(const Duration(seconds: 1), (_) {
  _tickCount++;
  _log('模拟同步任务 tick #$_tickCount');
});

// 回前台时对比:如果 Dart 任务能在后台继续跑,missed 应恒为 0
final wallSeconds = DateTime.now().difference(startedAt).inSeconds;
final missed = wallSeconds - _tickCount;
_log('量化:墙钟走了约 $wallSeconds 秒,任务只执行了 $_tickCount 次,'
    '约 $missed 秒被系统挂起吞掉');

为什么看这段:Timer.periodic 是典型「Dart 运行时任务」,它不会自动升级成后台服务;missed ≈ 后台停留秒数 就是「isolate 不等于保活」的可观测证据。

完整源码:background_boundary_demo_page.dart

3. 运行方式 ​

bash
cd labs/flutter/host
flutter pub get
flutter run -d macos   # 或 ios / android(真机效果最明显)
# 首页进入「Flutter 后台边界演示」

4. 预期现象与观察重点 ​

场景 A:启动模拟任务并停留前台 ​

预期顺序:

text
已启动模拟后台同步任务(Timer.periodic 1s)
→ 模拟同步任务 tick #1 → #2 → #3 ...

观察点:

  • 前台每秒稳定追加一条 tick

场景 B:切到后台再回来(核心场景) ​

预期行为:

  • 切后台瞬间日志追加 inactive/hidden/paused
  • 后台停留期间没有任何新 tick
  • 回前台追加 resumed + 一条量化日志,形如:
text
量化:墙钟走了约 12 秒,任务只执行了 3 次,约 9 秒被系统挂起吞掉

观察点:

  • missed 是否约等于你在后台停留的时长
  • 页面状态没有销毁(日志历史还在),但任务确实停了 —— 这就是边界本身

场景 C:桌面端对照(macOS) ​

预期行为:

  • macOS 上窗口最小化/遮挡的行为与移动端不同,tick 可能不会立刻冻结

观察点:

  • 同一份 Dart 代码在不同宿主上后台约束不一致,说明边界由平台决定,不由 Flutter 决定

5. 常见误区 ​

误区实际情况
Timer / Future 在退后台后还能稳定跑paused 后定时器被冻结,回前台也不会补跑错过的 tick
开一个 isolate 就能后台保活isolate 解决计算隔离,不改变系统对进程的后台授权
收到 detached 就能做清理detached 仅部分平台在终止前出现,不能当可靠回调
页面日志还在 = 任务一直在执行状态保留与执行窗口是两回事:内存活着 ≠ 时间片在走

6. 复习检查题 ​

  1. 为什么 Dart 定时器进后台就停了?

    答:因为 App 进入 paused 后宿主系统挂起/限制进程执行,Dart 运行时拿不到时间片;这是平台约束,不是代码问题。

  2. 这个实验怎么证明「isolate 不是保活方案」?

    答:resumed 后的量化日志显示墙钟时间与实际执行次数的差值约等于后台时长——即使把任务挪进 isolate,进程级挂起同样会冻结它。

  3. 需要可靠完成的下载/同步应该落在哪一层?

    答:原生层(Android WorkManager/FGS、iOS BGTask 等)承接执行与授权,Flutter 只做调用入口与前台展示。

7. 对应知识库文档 ​

站点构建时间:2026/8/24 23:43:17