Initial commit: 91dj android client dev_1.0.0
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,160 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user