diff --git a/lib/main.dart b/lib/main.dart index 3fd872c..9306319 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:media_kit/media_kit.dart'; -//import 'package:audio_service/audio_service.dart' as audio_service; +import 'package:audio_service/audio_service.dart' as audio_service; import 'package:permission_handler/permission_handler.dart'; import 'services/audio_service.dart'; @@ -9,6 +9,7 @@ import 'services/playback_service.dart'; import 'services/app_lifecycle_service.dart'; import 'services/webdav_service.dart'; import 'services/audio_player_handler.dart'; +import 'services/app_lifecycle_manager.dart'; import 'pages/home_page.dart'; import 'pages/player_page.dart'; import 'pages/playlist_page.dart'; @@ -23,6 +24,9 @@ bool _appInitialized = false; // ⭐ 全局缓存 Handler AudioPlayerHandler? _audioHandler; +// ⭐ 全局缓存 LifecycleManager +final AppLifecycleManager lifecycleManager = AppLifecycleManager(); + void main() async { // ⭐ P0:防止重复初始化 if (_appInitialized) { @@ -53,6 +57,10 @@ void main() async { providers: [ ChangeNotifierProvider(create: (_) => AudioService()), ChangeNotifierProvider(create: (_) => AppLifecycleService()), + // ⭐ 生命周期管理器 + ChangeNotifierProvider( + create: (_) => lifecycleManager, + ), Provider(create: (_) => _audioHandler!), ], child: const QTPlayerApp(), @@ -121,8 +129,14 @@ class QTPlayerApp extends StatefulWidget { } class _QTPlayerAppState extends State { + // ⭐ build 计数器(诊断用) + static int _buildCount = 0; + bool _isInitializing = false; + // ⭐ 保存生命周期监听器引用 + void Function(AppLifecycleStatus)? _lifecycleListener; + // ============================================================ // 生命周期 // ============================================================ @@ -134,92 +148,143 @@ class _QTPlayerAppState extends State { WidgetsBinding.instance.addPostFrameCallback((_) { debugPrint('⏱️ T2 首帧回调: ${stopwatch.elapsedMilliseconds}ms'); + + // ⭐ 1. 初始化生命周期管理器 + lifecycleManager.init(); + + // ⭐ 2. 绑定生命周期到播放器 + _bindLifecycleToPlayback(); + + // ⭐ 3. 后台初始化 _startBackgroundInitialization(); }); } @override void dispose() { + // ⭐ 移除生命周期监听器 + if (_lifecycleListener != null) { + lifecycleManager.removeListenerFromManager(_lifecycleListener!); + } super.dispose(); } // ============================================================ - // 后台初始化(不阻塞首帧) + // 生命周期 → 播放器绑定 + // ============================================================ + void _bindLifecycleToPlayback() { + if (_lifecycleListener != null) { + lifecycleManager.removeListenerFromManager(_lifecycleListener!); + } + + _lifecycleListener = (status) { + final audioService = context.read(); + + switch (status) { + case AppLifecycleStatus.paused: + case AppLifecycleStatus.inactive: + debugPrint('🎵 App 进入后台,播放继续'); + break; + + case AppLifecycleStatus.resumed: + debugPrint('🎵 App 回到前台'); + break; + + case AppLifecycleStatus.backgroundIdle: + debugPrint('🎵 长后台唤醒,检查播放状态'); + break; + + default: + break; + } + }; + + lifecycleManager.addListenerToManager(_lifecycleListener!); + } + + // ============================================================ + // 后台初始化(精细时间切片) // ============================================================ Future _startBackgroundInitialization() async { if (_isInitializing) return; _isInitializing = true; + final tStart = DateTime.now(); debugPrint('⏱️ T3 后台初始化开始'); - // 1. PlaybackService(轻量级) + // ----- 1. PlaybackService ----- + final t1 = DateTime.now(); try { PlaybackService().init(); - debugPrint('⏱️ T3.1 PlaybackService 初始化完成'); + debugPrint('⏱️ T3.1 PlaybackService 完成: ${_elapsedMs(t1)}ms'); } catch (e) { - debugPrint('❌ PlaybackService 初始化失败: $e'); + debugPrint('❌ PlaybackService 失败: $e'); } - // 2. ⭐ AudioService 暂时禁用(等 MainActivity 改为 AudioServiceActivity 后启用) - // await _initAudioService(); - - // 3. WebDAV - await _initWebDAV(); - - // 4. 通知权限 - await _requestPermissions(); - - debugPrint('⏱️ T4 全部初始化完成'); - } - - // ════════════════════════════════════════════════════════════ - // 子初始化方法 - // ════════════════════════════════════════════════════════════ - - // ⭐ 暂时禁用,等 MainActivity 改为 AudioServiceActivity 后启用 - // Future _initAudioService() async { - // try { - // await audio_service.AudioService.init( - // builder: () => _audioHandler!, - // config: const audio_service.AudioServiceConfig( - // androidNotificationChannelId: 'com.example.qt_player.music', - // androidNotificationChannelName: '清听音乐播放', - // androidNotificationOngoing: true, - // androidStopForegroundOnPause: true, - // ), - // ); - // debugPrint('✅ AudioService 初始化完成'); - // } catch (e) { - // debugPrint('❌ AudioService 初始化失败: $e'); - // } - // } - - Future _initWebDAV() async { + // ----- 2. WebDAV(只加载凭证) ----- + final t2 = DateTime.now(); try { final hasCred = await WebDAVService.instance.loadCredentials(); - debugPrint('✅ WebDAV 加载完成, 已连接: $hasCred'); + debugPrint('⏱️ T3.2 WebDAV 凭证加载完成: ${_elapsedMs(t2)}ms, 有凭证: $hasCred'); } catch (e) { - debugPrint('❌ WebDAV 加载失败: $e'); + debugPrint('❌ WebDAV 凭证加载失败: $e'); } - } - Future _requestPermissions() async { + // ----- 3. ⭐ AudioService(后台播放核心) ----- + await _initAudioService(); + + // ----- 4. 通知权限 ----- + final t3 = DateTime.now(); try { if (await Permission.notification.isDenied) { - final status = await Permission.notification.request(); - debugPrint('📢 通知权限状态: $status'); + await Permission.notification.request(); + debugPrint('⏱️ T3.3 通知权限完成: ${_elapsedMs(t3)}ms'); + } else { + debugPrint('⏱️ T3.3 通知权限已授予: ${_elapsedMs(t3)}ms'); } } catch (e) { debugPrint('❌ 权限申请失败: $e'); } + + final tEnd = DateTime.now(); + debugPrint('⏱️ T4 全部初始化完成: ${tEnd.difference(tStart).inMilliseconds}ms'); } - // ============================================================ + // ════════════════════════════════════════════════════════════ + // ⭐ AudioService 初始化(后台播放核心) + // ════════════════════════════════════════════════════════════ + Future _initAudioService() async { + try { + await audio_service.AudioService.init( + builder: () => _audioHandler!, + config: const audio_service.AudioServiceConfig( + androidNotificationChannelId: 'com.example.qt_player.music', + androidNotificationChannelName: '清听音乐播放', + androidNotificationOngoing: true, + androidStopForegroundOnPause: true, + ), + ); + debugPrint('✅ AudioService 初始化完成'); + } catch (e) { + debugPrint('❌ AudioService 初始化失败: $e'); + } + } + + // ════════════════════════════════════════════════════════════ + // 辅助方法 + // ════════════════════════════════════════════════════════════ + String _elapsedMs(DateTime start) { + return '${DateTime.now().difference(start).inMilliseconds}'; + } + + // ════════════════════════════════════════════════════════════ // Build - // ============================================================ + // ════════════════════════════════════════════════════════════ @override Widget build(BuildContext context) { - debugPrint('⏱️ T1 build: ${stopwatch.elapsedMilliseconds}ms'); + _buildCount++; + debugPrint( + '🎨 [QTPlayerApp] build #$_buildCount ${stopwatch.elapsedMilliseconds}ms'); // 监听歌曲变化,更新通知栏 final audioService = context.watch(); diff --git a/lib/services/app_lifecycle_manager.dart b/lib/services/app_lifecycle_manager.dart new file mode 100644 index 0000000..166ca4c --- /dev/null +++ b/lib/services/app_lifecycle_manager.dart @@ -0,0 +1,189 @@ +// lib/services/app_lifecycle_manager.dart +import 'dart:async'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:media_kit/media_kit.dart'; + +/// App 生命周期状态(扩展了 Flutter 原生状态) +enum AppLifecycleStatus { + /// 应用正在启动(初始化阶段) + launching, + + /// 应用在前台,用户可交互 + resumed, + + /// 应用在前台但不可交互(如:来电、系统弹窗) + inactive, + + /// 应用在后台 + paused, + + /// 应用即将被销毁 + detached, + + /// 应用已进入后台且播放停止(自定义,用于播放策略) + backgroundIdle, +} + +/// App 生命周期管理器 +/// +/// 职责: +/// 1. 统一监听 Flutter 的 AppLifecycleState +/// 2. 管理播放器在前后台的行为 +/// 3. 管理渲染调度器的运行状态 +/// 4. 管理 WebDAV 的缓存策略 +class AppLifecycleManager extends ChangeNotifier { + static final AppLifecycleManager _instance = AppLifecycleManager._internal(); + factory AppLifecycleManager() => _instance; + AppLifecycleManager._internal(); + + // ---- 当前状态 ---- + AppLifecycleStatus _status = AppLifecycleStatus.launching; + AppLifecycleStatus get status => _status; + + // ---- 上次进入后台的时间 ---- + DateTime? _backgroundedAt; + DateTime? get backgroundedAt => _backgroundedAt; + + // ---- 是否在后台 ---- + bool get isInBackground => + _status == AppLifecycleStatus.paused || + _status == AppLifecycleStatus.inactive || + _status == AppLifecycleStatus.detached; + + bool get isInForeground => _status == AppLifecycleStatus.resumed; + + // ---- 后台时长 ---- + Duration get backgroundDuration { + if (_backgroundedAt == null) return Duration.zero; + return DateTime.now().difference(_backgroundedAt!); + } + + // ---- 是否处于"长后台"(超过 5 分钟) ---- + bool get isLongBackground => backgroundDuration > const Duration(minutes: 5); + + // ---- 监听器 ---- + final List _listeners = []; + + // ---- Flutter 原生生命周期订阅 ---- + late final WidgetsBindingObserver _observer; + bool _isObserving = false; + + // ---- 初始化 ---- + void init() { + if (_isObserving) return; + _isObserving = true; + + _observer = _AppLifecycleObserver(this); + WidgetsBinding.instance.addObserver(_observer); + + // 获取初始状态 + _updateStatus(AppLifecycleStatus.resumed); + + debugPrint('📱 AppLifecycleManager 已初始化'); + } + + // ---- 状态更新 ---- + void _handleNativeState(AppLifecycleState state) { + final newStatus = _convertNativeState(state); + + // 状态变化时更新 + if (newStatus != _status) { + _updateStatus(newStatus); + } + + // 记录进入后台的时间 + if (newStatus == AppLifecycleStatus.paused || + newStatus == AppLifecycleStatus.inactive) { + if (_backgroundedAt == null) { + _backgroundedAt = DateTime.now(); + debugPrint('📱 App 进入后台: ${_backgroundedAt}'); + } + } + + // 回到前台时,如果是长后台,触发通知 + if (newStatus == AppLifecycleStatus.resumed) { + if (_backgroundedAt != null) { + final duration = backgroundDuration; + debugPrint('📱 App 回到前台,后台时长: ${duration.inMinutes}分钟'); + _backgroundedAt = null; + + // 如果是长后台,通知监听器做重连等操作 + if (duration > const Duration(minutes: 5)) { + _notifyListeners(AppLifecycleStatus.backgroundIdle); + } + } + } + } + + void _updateStatus(AppLifecycleStatus newStatus) { + final oldStatus = _status; + _status = newStatus; + debugPrint('📱 生命周期状态变化: $oldStatus → $newStatus'); + notifyListeners(); + _notifyListeners(newStatus); + } + + AppLifecycleStatus _convertNativeState(AppLifecycleState state) { + switch (state) { + case AppLifecycleState.resumed: + return AppLifecycleStatus.resumed; + case AppLifecycleState.inactive: + return AppLifecycleStatus.inactive; + case AppLifecycleState.paused: + return AppLifecycleStatus.paused; + case AppLifecycleState.detached: + return AppLifecycleStatus.detached; + default: + return AppLifecycleStatus.paused; + } + } + + // ---- 自定义监听器管理 ---- + void addListenerToManager(void Function(AppLifecycleStatus) listener) { + _listeners.add(listener); + } + + void removeListenerFromManager(void Function(AppLifecycleStatus) listener) { + _listeners.remove(listener); + } + + void _notifyListeners(AppLifecycleStatus status) { + for (final listener in _listeners) { + try { + listener(status); + } catch (e) { + debugPrint('❌ 生命周期监听器执行失败: $e'); + } + } + } + + // ---- 检查是否需要重新连接 WebDAV ---- + bool get shouldRecheckWebDAV { + if (!isInForeground) return false; + if (_backgroundedAt == null) return false; + return isLongBackground; + } + + // ---- 释放 ---- + void dispose() { + if (_isObserving) { + WidgetsBinding.instance.removeObserver(_observer); + _isObserving = false; + } + _listeners.clear(); + super.dispose(); + } +} + +/// 内部观察者(绑定 WidgetsBinding) +class _AppLifecycleObserver extends WidgetsBindingObserver { + final AppLifecycleManager _manager; + + _AppLifecycleObserver(this._manager); + + @override + void didChangeAppLifecycleState(AppLifecycleState state) { + _manager._handleNativeState(state); + } +}