import 'dart:async'; import 'package:analytics_sdk/entity/app_lifecycle_event.dart'; import 'package:analytics_sdk/manager/session_manager.dart'; import 'package:analytics_sdk/observer/page_lifecycle_observer.dart'; import 'package:analytics_sdk/utils/environment_info_util.dart'; import 'package:analytics_sdk/utils/logger.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/widgets.dart'; /// 应用生命周期观察者:监听应用前后台切换,用于会话管理与生命周期埋点 class AppLifecycleObserver with WidgetsBindingObserver { static final AppLifecycleObserver instance = AppLifecycleObserver._internal(); factory AppLifecycleObserver() => instance; AppLifecycleObserver._internal(); bool _isInitialized = false; void Function(dynamic event)? _track; DateTime Function() _now = DateTime.now; DateTime? _foregroundStartedAt; DateTime? _backgroundStartedAt; int _foregroundCountInSid = 0; String _foregroundCountSid = ''; /// 初始化观察者 void initialize({ void Function(dynamic event)? track, DateTime Function()? now, }) { if (track != null) _track = track; if (now != null) _now = now; _foregroundStartedAt ??= _now(); if (!_isInitialized) { WidgetsBinding.instance.addObserver(this); _isInitialized = true; Logger.analyticsSdk('应用生命周期观察者已初始化'); } } /// 销毁观察者 void dispose() { if (_isInitialized) { WidgetsBinding.instance.removeObserver(this); _isInitialized = false; Logger.analyticsSdk('应用生命周期观察者已销毁'); } _track = null; _foregroundStartedAt = null; _backgroundStartedAt = null; _foregroundCountInSid = 0; _foregroundCountSid = ''; } @override void didChangeAppLifecycleState(AppLifecycleState state) { super.didChangeAppLifecycleState(state); try { switch (state) { case AppLifecycleState.resumed: if (!kIsWeb) { final now = _now(); final backgroundStartedAt = _backgroundStartedAt; final backgroundDurationMs = backgroundStartedAt == null ? 0 : now.difference(backgroundStartedAt).inMilliseconds; // 应用从后台恢复前台 SessionManager.instance.onAppForeground(); if (backgroundStartedAt != null) { _emit(AppLifecycleEvent.warmStart( backgroundDurationMs: backgroundDurationMs, resumeReason: 'unknown', )); _emit(AppLifecycleEvent.foreground( foregroundCountInSid: _nextForegroundCountInSid(), )); } _backgroundStartedAt = null; _foregroundStartedAt = now; } unawaited( EnvironmentInfoUtil.refreshBatteryLevelIfNeeded(force: true), ); unawaited(EnvironmentInfoUtil.refreshRuntimeObservationFields()); Logger.analyticsSdk('应用恢复前台'); break; case AppLifecycleState.paused: if (!kIsWeb) { final now = _now(); final foregroundDurationMs = _foregroundStartedAt == null ? 0 : now.difference(_foregroundStartedAt!).inMilliseconds; _emit(AppLifecycleEvent.background( foregroundDurationMs: foregroundDurationMs, lastActivePageKey: PageLifecycleObserver.currentPageKey, )); // Android 和 iOS 进入后台均走 paused(iOS 先经过 hidden 再到 paused) // 统一在此记录后台开始时间,避免在 hidden 重复记录 SessionManager.instance.onAppBackground(); _backgroundStartedAt = now; } Logger.analyticsSdk('应用进入后台'); break; case AppLifecycleState.inactive: // inactive 是过渡状态(进后台和回前台都会经过),不记录后台时间 Logger.analyticsSdk('应用进入 inactive 状态'); break; case AppLifecycleState.detached: if (!kIsWeb) { final now = _now(); _emit(AppLifecycleEvent.terminate( lastActivePageKey: PageLifecycleObserver.currentPageKey, terminateType: 'unknown', sessionDurationMs: _sessionDurationMs(now), terminateTs: now.millisecondsSinceEpoch ~/ 1000, )); } Logger.analyticsSdk('应用即将终止'); break; default: // Flutter 3.0 没有 hidden;较新版本的 hidden 是过渡状态, // 不应影响会话开始或后台时长。 Logger.analyticsSdk('应用生命周期过渡状态: $state'); break; } } catch (e) { Logger.analyticsSdk('应用生命周期状态变化处理异常: $e'); _emit(AppLifecycleEvent.unknown(unknownReason: 'collect_error')); } } void _emit(AppLifecycleEvent event) { try { final track = _track; if (track == null) return; track(event); } catch (e) { Logger.analyticsSdk('app_lifecycle 上报异常,已安全处理: $e'); } } int _nextForegroundCountInSid() { final sid = SessionManager.instance.currentSessionId ?? ''; if (sid != _foregroundCountSid) { _foregroundCountSid = sid; _foregroundCountInSid = 0; } _foregroundCountInSid += 1; return _foregroundCountInSid; } int _sessionDurationMs(DateTime now) { final start = SessionManager.instance.sessionStartTime; if (start == null) return 0; final duration = now.difference(start).inMilliseconds; return duration < 0 ? 0 : duration; } }