Commit e41da12768beaa89120034b07210bdc5a0699c24

Authored by 权海
1 parent 80b26c33

feat(ui):数据流转统一成一个日志

... ... @@ -14,12 +14,14 @@ import '../../data/local/user_account_storage.dart';
import '../../data/local/user_preferences_storage.dart';
import '../../core/services/loading_service.dart';
import '../../core/services/native_health_data_update_bridge.dart';
import '../../core/services/raw_data_service/platform_ios/apple_health_raw_data_core_service.dart';
import '../bindings/initial_binding.dart';
/// Application startup before [runApp].
abstract final class AppBootstrap {
static Future<void> init() async {
WidgetsFlutterBinding.ensureInitialized();
AppleHealthRawDataCoreService.setExecutionEngineType('main');
AppLogger.init();
await UserAgentProvider.init();
... ... @@ -55,6 +57,7 @@ abstract final class AppBootstrap {
static Future<void> initForBackground() async {
WidgetsFlutterBinding.ensureInitialized();
AppleHealthRawDataCoreService.setExecutionEngineType('temporary');
AppLogger.init();
await UserAgentProvider.init();
... ...
... ... @@ -35,9 +35,7 @@ class DeveloperOptionsItem {
enum DeveloperOptionsAction {
shareAppDiagnosticLog,
shareHealthRawDataDatabase,
shareNativeAppleHealthObserverRecord,
shareFlutterObserverRecord,
shareLocalNotificationDebugRecord,
shareHealthPipelineRecord,
sendTestHrvLocalNotification,
pullOhosRawData,
clearHealthRawDataDatabaseAndUploadLog,
... ... @@ -85,16 +83,8 @@ class DeveloperOptionsController extends GetxController {
action: DeveloperOptionsAction.shareHealthRawDataDatabase,
),
DeveloperOptionsItem(
title: '分享原生响应AppleHealth记录',
action: DeveloperOptionsAction.shareNativeAppleHealthObserverRecord,
),
DeveloperOptionsItem(
title: '分享Flutter 响应AppleHealth回调记录',
action: DeveloperOptionsAction.shareFlutterObserverRecord,
),
DeveloperOptionsItem(
title: '分享本地推送诊断日志',
action: DeveloperOptionsAction.shareLocalNotificationDebugRecord,
title: '分享 Health 数据流程日志',
action: DeveloperOptionsAction.shareHealthPipelineRecord,
),
DeveloperOptionsItem(
title: '测试本地推送 HRV',
... ... @@ -124,16 +114,8 @@ class DeveloperOptionsController extends GetxController {
action: DeveloperOptionsAction.shareHealthRawDataDatabase,
),
DeveloperOptionsItem(
title: '分享原生响应AppleHealth记录',
action: DeveloperOptionsAction.shareNativeAppleHealthObserverRecord,
),
DeveloperOptionsItem(
title: '分享Flutter 响应AppleHealth回调记录',
action: DeveloperOptionsAction.shareFlutterObserverRecord,
),
DeveloperOptionsItem(
title: '分享本地推送诊断日志',
action: DeveloperOptionsAction.shareLocalNotificationDebugRecord,
title: '分享 Health 数据流程日志',
action: DeveloperOptionsAction.shareHealthPipelineRecord,
),
DeveloperOptionsItem(
title: '主动拉取 OHOS 原始数据',
... ... @@ -161,14 +143,8 @@ class DeveloperOptionsController extends GetxController {
case DeveloperOptionsAction.shareHealthRawDataDatabase:
await shareHealthRawDataDatabase();
break;
case DeveloperOptionsAction.shareNativeAppleHealthObserverRecord:
await shareNativeAppleHealthObserverRecord();
break;
case DeveloperOptionsAction.shareFlutterObserverRecord:
await shareFlutterObserverRecord();
break;
case DeveloperOptionsAction.shareLocalNotificationDebugRecord:
await shareLocalNotificationDebugRecord();
case DeveloperOptionsAction.shareHealthPipelineRecord:
await shareHealthPipelineRecord();
break;
case DeveloperOptionsAction.sendTestHrvLocalNotification:
await sendTestHrvLocalNotification();
... ... @@ -248,32 +224,6 @@ class DeveloperOptionsController extends GetxController {
}
}
Future<void> shareNativeAppleHealthObserverRecord() async {
try {
await _healthRawDataCoreService.shareNativeAppleHealthObserverRecord();
} catch (error, stackTrace) {
AppLogger.e(
'Share native AppleHealthObserver record failed',
error,
stackTrace,
);
Get.snackbar('Share failed', error.toString());
}
}
Future<void> shareFlutterObserverRecord() async {
try {
await _healthRawDataCoreService.shareFlutterObserverRecord();
} catch (error, stackTrace) {
AppLogger.e(
'Share Flutter Observer-Caculate record failed',
error,
stackTrace,
);
Get.snackbar('Share failed', error.toString());
}
}
Future<void> shareUploadTaskRecord() async {
try {
await _healthRawDataCoreService.shareUploadTaskRecord();
... ... @@ -287,17 +237,15 @@ class DeveloperOptionsController extends GetxController {
}
}
Future<void> shareLocalNotificationDebugRecord() async {
Future<void> shareHealthPipelineRecord() async {
try {
final sharedByNative =
await _healthRawDataCoreService.shareLocalNotificationDebugRecord();
if (sharedByNative) return;
final text =
await _healthRawDataCoreService.readLocalNotificationDebugLogText();
await _platformHostApi.shareText(text);
Get.snackbar('Share failed', 'Health data pipeline log is unavailable');
} catch (error, stackTrace) {
AppLogger.e(
'Share local notification debug record failed',
'Share health pipeline record failed',
error,
stackTrace,
);
... ...
... ... @@ -54,7 +54,7 @@ class CustomThemeCard extends StatelessWidget {
: SizedBox(
height: 112,
child: ListView.separated(
scrollDirection: Axis.horizontal,
scrollDirection: Axis.vertical,
padding: EdgeInsets.zero,
itemBuilder: (context, index) {
return _CustomThemeItem(
... ...
... ... @@ -24,6 +24,11 @@ import '../health_sleep_calculator.dart';
class AppleHealthRawDataCoreService {
static const int defaultLookbackDays = 183;
static const int defaultReadChunkDays = 7;
static String _executionEngineType = 'main';
static void setExecutionEngineType(String value) {
_executionEngineType = value;
}
AppleHealthRawDataCoreService({
HealthKitHostApi? healthApi,
... ... @@ -107,6 +112,11 @@ class AppleHealthRawDataCoreService {
_healthDataUpdatedController.stream;
Future<bool> onHealthDataUpdated({List<int>? dataTypes}) async {
await _saveLocalNotificationDebugEvent(<String, Object?>{
'event': 'flutterHealthDataReceived',
'identifier': 'flutter.health_data.received',
'data_types': dataTypes ?? const <int>[],
});
await _saveNativeCallFlutterChange(dataTypes);
if (_healthDataUpdatedCalculation != null) {
_pendingHealthDataUpdatedTypes = _mergeHealthDataTypes(
... ... @@ -282,6 +292,11 @@ class AppleHealthRawDataCoreService {
}
final userId = _userId;
await _saveLocalNotificationDebugEvent(<String, Object?>{
'event': 'flutterCalculationStarted',
'identifier': 'flutter.calculation.started',
'user_id': userId,
});
final hasAuthorization = await _hasHealthReadAuthorization();
if (!hasAuthorization) {
return HealthRawStressCalculationResult(
... ... @@ -392,6 +407,21 @@ class AppleHealthRawDataCoreService {
),
);
await _localStore.upsertResult(newResult);
await _saveLocalNotificationDebugEvent(<String, Object?>{
'event': 'flutterCalculationResultStored',
'identifier': 'flutter.calculation.result_stored',
'user_id': userId,
'hrv_count': newResult.hrvStressPoints.length,
'realtime_count': newResult.realtimeStressPoints.length,
'latest_hrv': _latestHrvStressPointPayload(newResult.hrvStressPoints),
'latest_realtime':
_latestRealtimeStressPointPayload(newResult.realtimeStressPoints),
});
await _sendLocalNotificationsAfterCalculation(
result: newResult,
hasExistingHrv: latestHrvRawEndTime != null,
hasExistingSleep: latestSleepResultTime != null,
);
_scheduleResultUpload('upload hrv results', _uploadHrvResults);
_scheduleResultUpload(
'upload realtime stress results',
... ... @@ -417,7 +447,10 @@ class AppleHealthRawDataCoreService {
sleepResults: sleepResults,
);
await _sendLocalNotificationsAfterCalculation(
result: storedResult,
result: storedResult.copyWith(
hrvStressPoints: const <HealthRawHrvStressPoint>[],
realtimeStressPoints: const <HealthRawRealtimeStressPoint>[],
),
hasExistingHrv: latestHrvRawEndTime != null,
hasExistingSleep: latestSleepResultTime != null,
);
... ... @@ -425,6 +458,15 @@ class AppleHealthRawDataCoreService {
_logInfo(
'calculateEndAt=${_formatDateTimeMilliseconds(calculateFinishedAt)}',
);
await _saveLocalNotificationDebugEvent(<String, Object?>{
'event': 'flutterCalculationFinished',
'identifier': 'flutter.calculation.finished',
'user_id': userId,
'hrv_count': storedResult.hrvStressPoints.length,
'realtime_count': storedResult.realtimeStressPoints.length,
'daily_stress_count': storedResult.dailyStressPoints.length,
'sleep_count': storedResult.sleepResults.length,
});
return storedResult;
}
... ... @@ -436,6 +478,7 @@ class AppleHealthRawDataCoreService {
await _saveLocalNotificationDebugEvent(
<String, Object?>{
'event': 'flutterNotificationCalculationFinished',
'identifier': 'flutter.notification.calculation_finished',
'user_id': result.userId,
'has_existing_hrv': hasExistingHrv,
'has_existing_sleep': hasExistingSleep,
... ... @@ -452,6 +495,7 @@ class AppleHealthRawDataCoreService {
await _saveLocalNotificationDebugEvent(
<String, Object?>{
'event': 'flutterNotificationRecordRead',
'identifier': 'flutter.notification.record_read',
'user_id': result.userId,
'record': _localNotificationRecordPayload(record),
},
... ... @@ -463,6 +507,7 @@ class AppleHealthRawDataCoreService {
await _saveLocalNotificationDebugEvent(
<String, Object?>{
'event': 'flutterNotificationLatestDataTimeChecked',
'identifier': 'flutter.notification.latest_data_checked',
'user_id': result.userId,
'record': _localNotificationRecordPayload(record),
'latest_data_time': candidateData.latestDataTimePayload,
... ... @@ -475,6 +520,7 @@ class AppleHealthRawDataCoreService {
await _saveLocalNotificationDebugEvent(
<String, Object?>{
'event': 'flutterNotificationNotBuilt',
'identifier': 'flutter.notification.not_built',
'reason': 'empty_calculation_result',
'user_id': result.userId,
},
... ... @@ -522,6 +568,7 @@ class AppleHealthRawDataCoreService {
await _saveLocalNotificationDebugEvent(
<String, Object?>{
'event': 'flutterNotificationBuildDecisions',
'identifier': 'flutter.notification.selection_result',
'user_id': candidateResult.userId,
'decisions': _localNotificationDecisionPayloads(
result: notificationResult,
... ... @@ -542,6 +589,7 @@ class AppleHealthRawDataCoreService {
await _saveLocalNotificationDebugEvent(
<String, Object?>{
'event': 'flutterNotificationBuildFinished',
'identifier': 'flutter.notification.selection_finished',
'user_id': candidateResult.userId,
'notification_count': notifications.length,
'notifications': notifications.map(_localNotificationPayload).toList(),
... ... @@ -551,6 +599,7 @@ class AppleHealthRawDataCoreService {
await _saveLocalNotificationDebugEvent(
<String, Object?>{
'event': 'flutterNotificationNotSent',
'identifier': 'flutter.notification.not_triggered',
'reason': 'no_built_notifications',
'user_id': candidateResult.userId,
},
... ... @@ -563,6 +612,7 @@ class AppleHealthRawDataCoreService {
await _saveLocalNotificationDebugEvent(
<String, Object?>{
'event': 'flutterNotificationSendStart',
'identifier': 'flutter.notification.triggered',
'user_id': candidateResult.userId,
'notification': _localNotificationPayload(notification),
},
... ... @@ -574,6 +624,7 @@ class AppleHealthRawDataCoreService {
await _saveLocalNotificationDebugEvent(
<String, Object?>{
'event': 'flutterNotificationSendFinished',
'identifier': 'flutter.notification.trigger_finished',
'user_id': candidateResult.userId,
'sent': sent,
'reason': sent ? null : 'native_or_dispatcher_returned_false',
... ... @@ -1325,6 +1376,11 @@ class AppleHealthRawDataCoreService {
if (!_uploadResultsAfterCalculation) {
return;
}
unawaited(_saveLocalNotificationDebugEvent(<String, Object?>{
'event': 'flutterCalculationResultUploadTriggered',
'identifier': 'flutter.calculation_result_upload.triggered',
'upload_action': action,
}));
unawaited(
(() async {
try {
... ... @@ -1505,14 +1561,21 @@ class AppleHealthRawDataCoreService {
Future<void> _saveLocalNotificationDebugEvent(
Map<String, Object?> event,
) async {
try {
await _localNotificationDebugStore.append(event);
await _saveNativeLocalNotificationDebugEvent(event);
} catch (error, stackTrace) {
_logError(
'save local notification debug event failed', error, stackTrace);
}
) {
final pipelineEvent = <String, Object?>{
...event,
'engine_type': _executionEngineType,
};
unawaited(() async {
try {
await _saveNativeLocalNotificationDebugEvent(pipelineEvent).timeout(
const Duration(seconds: 1),
);
} catch (error, stackTrace) {
_logError('save health pipeline event failed', error, stackTrace);
}
}());
return Future<void>.value();
}
Future<bool> _saveNativeLocalNotificationDebugEvent(
... ...
... ... @@ -12,6 +12,7 @@ import 'core/services/raw_data_service/health_raw_data_core_service.dart';
import 'core/services/raw_data_service/health_raw_models.dart';
import 'core/theme/app_theme.dart';
import 'data/local/user_preferences_storage.dart';
import 'pigeon/health_kit_raw_data_api.g.dart';
Future<void> main() async {
runZonedGuarded<Future<Null>>(
... ... @@ -77,6 +78,7 @@ Future<void> _runPendingHealthTasks(
channel,
'tasks.received count=${tasks.length} ids=${tasks.map((e) => e.taskId).join(',')}',
);
await _recordTemporaryHealthTaskReceived(tasks);
if (tasks.isEmpty) return;
for (final task in tasks) {
... ... @@ -133,6 +135,25 @@ Future<void> _runPendingHealthTasks(
}
}
Future<void> _recordTemporaryHealthTaskReceived(
List<_BackgroundHealthTask> tasks,
) async {
try {
await HealthKitRawDataHostApi().saveLocalNotificationDebugEvent(
event: <String, Object?>{
'event': 'flutterHealthDataReceived',
'identifier': 'flutter.health_data.received',
'engine_type': 'temporary',
'task_ids': tasks.map((task) => task.taskId).toList(growable: false),
'data_types': tasks.expand((task) => task.dataTypes).toSet().toList()
..sort(),
},
).timeout(const Duration(seconds: 1));
} catch (_) {
// Pipeline telemetry must never delay a HealthKit background task.
}
}
bool _hasNewHealthCalculationResult(HealthRawStressCalculationResult result) {
return result.hrvStressPoints.isNotEmpty ||
result.realtimeStressPoints.isNotEmpty ||
... ... @@ -164,13 +185,22 @@ List<_BackgroundHealthTask> _backgroundHealthTasksFromArguments(
}
class _BackgroundHealthTask {
const _BackgroundHealthTask({required this.taskId});
const _BackgroundHealthTask({
required this.taskId,
required this.dataTypes,
});
final String taskId;
final List<int> dataTypes;
static _BackgroundHealthTask? fromMap(Map<Object?, Object?> map) {
final taskId = map['taskId'];
if (taskId is! String || taskId.isEmpty) return null;
return _BackgroundHealthTask(taskId: taskId);
final dataTypes = (map['dataTypes'] as Iterable<Object?>?)
?.whereType<num>()
.map((value) => value.toInt())
.toList(growable: false) ??
const <int>[];
return _BackgroundHealthTask(taskId: taskId, dataTypes: dataTypes);
}
}
... ...