Files
hjsq_android/analytics_sdk/lib/entity/app_lifecycle_event.dart
T
2026-10-08 11:37:25 +07:00

190 lines
5.5 KiB
Dart

import 'dart:convert';
import 'package:analytics_sdk/const/event_type.dart';
import 'package:analytics_sdk/manager/sdk_context.dart';
/// 应用生命周期事件。
///
/// `app_lifecycle` 的子事件字段写入 `payload`。每个工厂方法只写入该
/// 子事件文档定义的字段。
class AppLifecycleEvent {
final String event = EventType.appLifecycle.event;
final String lifecycleEventKey;
final Map<String, dynamic> _fields;
late final String eventId;
late final Map<String, dynamic> _commonFields;
AppLifecycleEvent._(
this.lifecycleEventKey,
Map<String, dynamic> fields,
) : _fields = Map<String, dynamic>.from(fields)
..removeWhere((_, value) => value == null) {
_commonFields = SdkContext.generateCommonFields(event);
eventId = SdkContext.generateEventIdFromCommonFields(_commonFields, [
lifecycleEventKey,
jsonEncode(_stableFields),
]);
}
factory AppLifecycleEvent.coldStart({
required int coldStartDurationMs,
String lastExitType = 'unknown',
int bootTime = 0,
}) =>
AppLifecycleEvent._('cold_start', {
'cold_start_duration_ms': _nonNegative(coldStartDurationMs),
'last_exit_type': _allowedString(
lastExitType,
const {'normal', 'killed', 'crash', 'unknown'},
'unknown',
),
'boot_time': bootTime > 0 ? bootTime : 0,
});
factory AppLifecycleEvent.webLoad({
required String pageUrl,
required int pageLoadTimeMs,
required int viewportWidth,
required int viewportHeight,
}) =>
AppLifecycleEvent._('web_load', {
'page_url': pageUrl,
'page_load_time_ms': _nonNegative(pageLoadTimeMs),
'viewport_width': viewportWidth,
'viewport_height': viewportHeight,
});
factory AppLifecycleEvent.warmStart({
required int backgroundDurationMs,
String resumeReason = 'unknown',
}) =>
AppLifecycleEvent._('warm_start', {
'background_duration_ms': _nonNegative(backgroundDurationMs),
'resume_reason': _allowedString(
resumeReason,
const {'user', 'push', 'scheme', 'unknown'},
'unknown',
),
});
factory AppLifecycleEvent.foreground({
required int foregroundCountInSid,
}) =>
AppLifecycleEvent._('foreground', {
'foreground_count_in_sid':
foregroundCountInSid < 1 ? 1 : foregroundCountInSid,
'is_foreground': 1,
});
factory AppLifecycleEvent.background({
required int foregroundDurationMs,
required String lastActivePageKey,
}) =>
AppLifecycleEvent._('background', {
'is_foreground': 0,
'foreground_duration_ms': _nonNegative(foregroundDurationMs),
'last_active_page_key': lastActivePageKey,
});
factory AppLifecycleEvent.webUnload({
required String pageUrl,
required String lastActivePageKey,
required int pageStayDurationMs,
String unloadReason = 'unknown',
required int scrollDepthPercentAtUnload,
required String webVisibilityState,
}) =>
AppLifecycleEvent._('web_unload', {
'page_url': pageUrl,
'last_active_page_key': lastActivePageKey,
'page_stay_duration_ms': _nonNegative(pageStayDurationMs),
'unload_reason': _allowedString(
unloadReason,
const {
'normal',
'visibility_hidden',
'navigation',
'tab_close',
'unknown',
},
'unknown',
),
'scroll_depth_percent_at_unload':
scrollDepthPercentAtUnload.clamp(0, 100),
'web_visibility_state': _allowedString(
webVisibilityState,
const {'visible', 'hidden', 'unknown'},
'unknown',
),
});
factory AppLifecycleEvent.terminate({
required String lastActivePageKey,
String terminateType = 'unknown',
required int sessionDurationMs,
required int terminateTs,
}) =>
AppLifecycleEvent._('terminate', {
'last_active_page_key': lastActivePageKey,
'terminate_type': _allowedString(
terminateType,
const {'user_quit', 'system_kill', 'app_finish', 'unknown'},
'unknown',
),
'session_duration_ms': _nonNegative(sessionDurationMs),
'terminate_ts': terminateTs > 0
? terminateTs
: DateTime.now().millisecondsSinceEpoch ~/ 1000,
});
factory AppLifecycleEvent.unknown({
String unknownReason = 'unknown',
}) =>
AppLifecycleEvent._('unknown', {
'unknown_reason': _allowedString(
unknownReason,
const {
'callback_missing',
'state_conflict',
'unsupported_platform',
'sdk_not_ready',
'system_restricted',
'route_unrecognized',
'collect_error',
'unknown',
},
'unknown',
),
});
Map<String, dynamic> toJson() {
return {
..._commonFields,
'event_id': eventId,
'payload': {
'lifecycle_event_key': lifecycleEventKey,
..._fields,
},
};
}
Map<String, dynamic> get _stableFields {
final sortedKeys = _fields.keys.toList()..sort();
return {
for (final key in sortedKeys) key: _fields[key],
};
}
static int _nonNegative(int value) => value < 0 ? 0 : value;
static String _allowedString(
String value,
Set<String> allowed,
String fallback,
) {
final normalized = value.trim();
return allowed.contains(normalized) ? normalized : fallback;
}
}