Files
xianyan/lib/core/services/crash_log_service.dart
Developer a5d8483797 chore: 完成多平台兼容性优化与资源更新
本次提交包含多项改进:
1. 新增Android启动页资源与配色配置,完善多版本主题适配
2. 全量替换Platform.pathSeparator为硬编码斜杠,修复Web平台路径兼容问题
3. 为大量文件系统操作添加kIsWeb守卫,优化Web端表现
4. 替换硬编码平台判断为platform_utils封装,统一平台检测逻辑
5. 移除冗余代码与默认参数,优化小部件性能
6. 新增Web端适配逻辑,处理不支持的原生功能
7. 更新鸿蒙兼容性工具,完善平台识别与路径处理
8. 优化设备注册错误捕获,避免非致命崩溃
9. 添加启动页图标与背景配置,优化首屏体验
2026-06-05 02:31:34 +08:00

222 lines
6.0 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/// ============================================================
/// 闲言APP — 崩溃日志服务
/// 创建时间: 2026-05-21
/// 更新时间: 2026-06-05
/// 作用: 持久化存储崩溃/异常日志,支持增删查清空,自动限制数量
/// 上次更新: 修复 Web 平台兼容性kIsWeb 时跳过文件操作,使用内存存储
/// ============================================================
import 'dart:convert';
import 'dart:io';
import 'package:flutter/foundation.dart' show kIsWeb;
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:path_provider/path_provider.dart';
import '../utils/logger.dart';
class CrashLogEntry {
const CrashLogEntry({
required this.id,
required this.errorId,
required this.time,
required this.error,
required this.stackTrace,
this.deviceInfo,
this.appVersion,
});
final String id;
final String errorId;
final DateTime time;
final String error;
final String stackTrace;
final String? deviceInfo;
final String? appVersion;
Map<String, dynamic> toJson() => {
'id': id,
'errorId': errorId,
'time': time.toIso8601String(),
'error': error,
'stackTrace': stackTrace,
if (deviceInfo != null) 'deviceInfo': deviceInfo,
if (appVersion != null) 'appVersion': appVersion,
};
factory CrashLogEntry.fromJson(Map<String, dynamic> json) => CrashLogEntry(
id: json['id'] as String,
errorId: json['errorId'] as String,
time: DateTime.parse(json['time'] as String),
error: json['error'] as String,
stackTrace: json['stackTrace'] as String,
deviceInfo: json['deviceInfo'] as String?,
appVersion: json['appVersion'] as String?,
);
String toDisplayText() {
final buffer = StringBuffer();
buffer.writeln('[$errorId] ${time.toIso8601String()}');
if (appVersion != null) buffer.writeln('版本: $appVersion');
if (deviceInfo != null) buffer.writeln('设备: $deviceInfo');
buffer.writeln();
buffer.writeln('Error:');
buffer.writeln(error);
buffer.writeln();
buffer.writeln('Stack Trace:');
buffer.writeln(stackTrace);
return buffer.toString();
}
}
class CrashLogService {
CrashLogService._();
static final CrashLogService instance = CrashLogService._();
static const _maxLogs = 100;
static const _fileName = 'crash_logs.json';
List<CrashLogEntry> _logs = [];
List<CrashLogEntry> get logs => List.unmodifiable(_logs);
int get count => _logs.length;
bool _loaded = false;
/// 获取日志文件Web 端返回 null
Future<File?> _getFile() async {
if (kIsWeb) return null;
final dir = await getApplicationSupportDirectory();
return File('${dir.path}/$_fileName');
}
/// 确保日志已加载
Future<void> ensureLoaded() async {
if (_loaded) return;
await load();
}
/// 加载日志Web 端仅使用内存存储(无持久化)
Future<void> load() async {
if (kIsWeb) {
_logs = [];
_loaded = true;
Log.d('CrashLogService: Web 平台,跳过文件加载');
return;
}
try {
final file = await _getFile();
if (file == null || !await file.exists()) {
_logs = [];
_loaded = true;
return;
}
final content = await file.readAsString();
final list = jsonDecode(content) as List<dynamic>;
_logs = list
.map((e) => CrashLogEntry.fromJson(e as Map<String, dynamic>))
.toList();
_logs.sort((a, b) => b.time.compareTo(a.time));
_loaded = true;
Log.d('CrashLogService: 已加载 ${_logs.length} 条崩溃日志');
} catch (e) {
Log.e('CrashLogService: 加载失败', e);
_logs = [];
_loaded = true;
}
}
/// 保存日志到文件Web 端跳过
Future<void> _save() async {
if (kIsWeb) return;
try {
final file = await _getFile();
if (file == null) return;
final json = jsonEncode(_logs.map((e) => e.toJson()).toList());
await file.writeAsString(json);
} catch (e) {
Log.e('CrashLogService: 保存失败', e);
}
}
Future<CrashLogEntry> addLog({
required String error,
required String stackTrace,
String? deviceInfo,
String? appVersion,
}) async {
await ensureLoaded();
final now = DateTime.now();
final id = now.millisecondsSinceEpoch.toString();
final errorId =
'ERR-${now.millisecondsSinceEpoch.toRadixString(36).toUpperCase()}';
final entry = CrashLogEntry(
id: id,
errorId: errorId,
time: now,
error: error,
stackTrace: stackTrace,
deviceInfo: deviceInfo,
appVersion: appVersion,
);
_logs.insert(0, entry);
if (_logs.length > _maxLogs) {
_logs.removeRange(_maxLogs, _logs.length);
}
await _save();
Log.d('CrashLogService: 新增崩溃日志 [$errorId]');
return entry;
}
Future<void> deleteLog(String id) async {
await ensureLoaded();
_logs.removeWhere((e) => e.id == id);
await _save();
}
Future<void> deleteLogs(List<String> ids) async {
await ensureLoaded();
final idSet = ids.toSet();
_logs.removeWhere((e) => idSet.contains(e.id));
await _save();
}
Future<void> clearAll() async {
_logs.clear();
await _save();
Log.i('CrashLogService: 已清空所有崩溃日志');
}
CrashLogEntry? getById(String id) {
for (final e in _logs) {
if (e.id == id) return e;
}
return null;
}
Future<String> exportAllAsText() async {
await ensureLoaded();
final buffer = StringBuffer();
buffer.writeln('闲言APP 崩溃日志导出');
buffer.writeln('导出时间: ${DateTime.now().toIso8601String()}');
buffer.writeln('条目数量: ${_logs.length}');
buffer.writeln('=' * 60);
for (final entry in _logs) {
buffer.writeln(entry.toDisplayText());
buffer.writeln('-' * 60);
}
return buffer.toString();
}
}
final crashLogServiceProvider = Provider<CrashLogService>((ref) {
final service = CrashLogService.instance;
ref.onDispose(() {});
return service;
});