161 lines
5.6 KiB
Dart
161 lines
5.6 KiB
Dart
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;
|
||
}
|
||
}
|