按今日日志定位:所谓"后台假死"其实是进程被系统回收 (13:14:46 ActivityManager: has died: prcp FGS,当时内存压力下批量杀进程)。 冷启动后旧代码会先联网 play→seek→pause,既慢又会和用户点播放打架。 - restorePlaybackState: 只恢复队列/进度/UI,不联网,不再调用 _loadRestoredPlayback - 新增 playOrResume(): UI 按钮、通知栏、耳机键统一入口; 冷启动首次播放走 _playCurrent(open + seek 到保存位置 + play) - AudioPlayerHandler.play() 改走 playOrResume() - 播放中每 10s 持久化一次进度,冷启动恢复更准确 - 窗口期收紧:网络轮询 10s→3s、切换去抖 3s→2s、 起播看门狗 15s→10s、中段卡死 30s→20s 真机验证(release): 冷启动 8s 内 play 调用 0 次(不再预加载) 按键播放:冷启动首次播放 → open 21ms → restored position 15.751s → 1.5s 出声
69 lines
2.2 KiB
Dart
69 lines
2.2 KiB
Dart
// lib/services/network_watcher.dart
|
|
import 'dart:async';
|
|
import 'dart:io';
|
|
|
|
import 'package:flutter/foundation.dart';
|
|
|
|
/// 监听本机网络接口的地址变化。
|
|
///
|
|
/// 为什么不直接依赖 mpv 自己恢复:HTTP 流是长连接,Wi-Fi 切换 / 移动数据切换
|
|
/// 会换掉出口 IP,旧连接直接失效。mpv 不一定会重连,通常表现为
|
|
/// "进度不动、缓冲一直转"。所以在网络变化时由上层主动重开当前歌曲。
|
|
///
|
|
/// 实现方式:每 10s 枚举一次 IPv4 地址,签名变化即视为网络切换。
|
|
/// 相比平台 Channel 的方案,它不需要新增权限或插件,
|
|
/// 且能覆盖"同一个 Wi-Fi 换了 AP / 换了出口"这类 transport 类型不变的情况。
|
|
class NetworkWatcher {
|
|
static final NetworkWatcher _instance = NetworkWatcher._internal();
|
|
factory NetworkWatcher() => _instance;
|
|
NetworkWatcher._internal();
|
|
|
|
Timer? _timer;
|
|
String? _lastSignature;
|
|
bool _ticking = false;
|
|
|
|
/// 网络签名变化时回调(已在内部做过去抖)
|
|
void Function()? onChanged;
|
|
|
|
void start({Duration interval = const Duration(seconds: 3)}) {
|
|
if (_timer != null) return;
|
|
_timer = Timer.periodic(interval, (_) => _tick());
|
|
_tick();
|
|
debugPrint('🌐 [NetworkWatcher] 已启动(每 ${interval.inSeconds}s 检查一次)');
|
|
}
|
|
|
|
void stop() {
|
|
_timer?.cancel();
|
|
_timer = null;
|
|
_lastSignature = null;
|
|
debugPrint('🌐 [NetworkWatcher] 已停止');
|
|
}
|
|
|
|
Future<void> _tick() async {
|
|
if (_ticking) return;
|
|
_ticking = true;
|
|
try {
|
|
final interfaces = await NetworkInterface.list(
|
|
type: InternetAddressType.IPv4,
|
|
includeLoopback: false,
|
|
);
|
|
final signature = interfaces
|
|
.map((i) => '${i.name}:${i.addresses.map((a) => a.address).join(',')}')
|
|
.join('|');
|
|
|
|
final previous = _lastSignature;
|
|
_lastSignature = signature;
|
|
|
|
// 首次只记录基线,不触发
|
|
if (previous != null && previous != signature) {
|
|
debugPrint('🌐 [NetworkWatcher] 检测到网络变化');
|
|
onChanged?.call();
|
|
}
|
|
} catch (e) {
|
|
debugPrint('⚠️ [NetworkWatcher] 枚举网络接口失败: $e');
|
|
} finally {
|
|
_ticking = false;
|
|
}
|
|
}
|
|
}
|