190 lines
5.5 KiB
Dart
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;
|
|
}
|
|
}
|