Files
51dj_android/analytics_sdk/lib/observer/app_lifecycle_observer.dart
T
2026-10-08 11:37:25 +07:00

161 lines
5.6 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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;
}
}