Initial commit: 91dj android client dev_1.0.0

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
谢宇宁
2026-10-08 11:37:25 +07:00
co-authored by Claude Opus 5.5
commit 97dd6a739a
1262 changed files with 119813 additions and 0 deletions
@@ -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;
}
}