feat: 多模块功能更新 - 文件传输/多语言/NFC/首页组件/进度美化等

- 文件传输: 设备发现、LAN发现服务优化
- NFC分享: provider和service增强
- 多语言: 16种语言翻译补全
- 首页: 句子详情面板、收藏页、离线页优化
- 我的: 成就、个人资料、签到、设置页面更新
- 新增: AR视图、进度美化页、Hive安全访问、鸿蒙兼容助手、共享组件
- iOS Widget: Intents扩展、XianyanWidget更新
- 鸿蒙: 6个卡片页面更新
- 其他: 路由注册、缓存配置、崩溃监控、TTS播放器等
This commit is contained in:
Developer
2026-06-04 04:08:31 +08:00
parent 74b615afc4
commit 67f26ff166
64 changed files with 6122 additions and 408 deletions

View File

@@ -0,0 +1,244 @@
// ============================================================
// 闲言APP — Hive 安全访问工具类
// 创建时间: 2026-06-04
// 更新时间: 2026-06-04
// 作用: 统一 Hive Box 访问守卫,解决 OHOS 冷启动时序问题
// 提供 lazy-init 守卫、自动重试、日志追踪
// 上次更新: 初始版本
// ============================================================
import 'dart:async';
import 'package:hive_ce/hive.dart';
import 'package:hive_flutter/hive_flutter.dart' as flutter_hive;
import 'package:xianyan/core/utils/logger.dart';
// ============================================================
// Hive 安全访问单例
// 统一管理所有 Hive Box 的打开和访问,防止冷启动时序问题
// ============================================================
class HiveSafeAccess {
HiveSafeAccess._();
static final HiveSafeAccess _instance = HiveSafeAccess._();
static HiveSafeAccess get instance => _instance;
// ============================================================
// 状态跟踪
// ============================================================
bool _hiveInitialized = false;
bool get isHiveInitialized => _hiveInitialized;
/// 已打开的 Box 缓存 (boxName -> Box实例)
final Map<String, dynamic> _openBoxes = {};
/// 正在打开中的 Box Future (防止并发重复打开)
final Map<String, Future<dynamic>> _openingBoxes = {};
// ============================================================
// 初始化
// ============================================================
/// 确保 Hive 已初始化(仅在首次调用时执行)
static Future<void> ensureInitialized() async {
if (_instance._hiveInitialized) return;
try {
Log.i('[HiveSafe] 执行 Hive.initFlutter()...');
await flutter_hive.Hive.initFlutter();
_instance._hiveInitialized = true;
Log.i('[HiveSafe] Hive 初始化完成');
} catch (e, st) {
Log.e('[HiveSafe] Hive 初始化失败', e, st);
rethrow;
}
}
// ============================================================
// 核心:安全获取 Box
// ============================================================
/// 安全获取已打开的 Box如果未打开则自动打开
/// [name] - Box 名称
/// [retryCount] - 重试次数默认3次
/// [retryDelay] - 重试间隔默认500ms
static Future<Box<T>> safeBox<T>({
required String name,
int retryCount = 3,
Duration retryDelay = const Duration(milliseconds: 500),
}) async {
return _instance._safeBoxInternal<T>(
name: name,
retryCount: retryCount,
retryDelay: retryDelay,
);
}
Future<Box<T>> _safeBoxInternal<T>({
required String name,
int retryCount = 3,
Duration retryDelay = const Duration(milliseconds: 500),
}) async {
// 1. 检查缓存
if (_openBoxes.containsKey(name)) {
return _openBoxes[name] as Box<T>;
}
// 2. 检查是否正在打开中(防止并发)
if (_openingBoxes.containsKey(name)) {
Log.d('[HiveSafe] 等待 Box 打开: $name');
return _openingBoxes[name] as Future<Box<T>>;
}
// 3. 执行打开流程
final openFuture = _openWithRetry<T>(
name: name,
retryCount: retryCount,
retryDelay: retryDelay,
);
_openingBoxes[name] = openFuture;
try {
final box = await openFuture;
_openBoxes[name] = box;
return box as Box<T>;
} finally {
_openingBoxes.remove(name);
}
}
/// 带重试的 Box 打开逻辑
Future<Box<dynamic>> _openWithRetry<T>({
required String name,
int retryCount = 3,
Duration retryDelay = const Duration(milliseconds: 500),
}) async {
// 确保 Hive 已初始化
if (!_hiveInitialized) {
await ensureInitialized();
}
for (int i = 0; i < retryCount; i++) {
try {
Log.d('[HiveSafe] 尝试打开 Box: $name (尝试 ${i + 1}/$retryCount)');
final box = await Hive.openBox<T>(name);
Log.i('[HiveSafe] Box 打开成功: $name');
return box;
} on HiveError catch (e) {
Log.w('[HiveSafe] HiveError 打开失败 ($name): ${e.message}');
if (i < retryCount - 1) {
await Future<void>.delayed(retryDelay);
}
} catch (e) {
Log.w('[HiveSafe] 打开 Box 失败 ($name): $e');
if (i < retryCount - 1) {
await Future<void>.delayed(retryDelay);
}
}
}
throw StateError('[HiveSafe] Box 打开最终失败: $name (已重试$retryCount次)');
}
// ============================================================
// 同步安全访问(用于已有缓存的情况)
// ============================================================
/// 同步获取 Box仅从缓存返回不会触发异步打开
/// 返回 null 表示 Box 未打开
static Box<T>? tryGetBox<T>(String name) {
final cached = _instance._openBoxes[name];
if (cached != null) {
return cached as Box<T>;
}
// fallback: 检查 Hive 是否已经打开了该 Box
try {
if (_instance._hiveInitialized && Hive.isBoxOpen(name)) {
final box = Hive.box<T>(name);
_instance._openBoxes[name] = box;
return box;
}
} on HiveError catch (e) {
Log.w('[HiveSafe] tryGetBox 失败 ($name): ${e.message}');
return null;
} catch (e) {
Log.w('[HiveSafe] tryGetBox 异常 ($name): $e');
return null;
}
return null;
}
// ============================================================
// 预打开 Box在应用启动时批量调用
// ============================================================
/// 批量预打开多个 Box
static Future<void> preOpenBoxes(List<String> boxNames) async {
Log.i('[HiveSafe] 开始预打开 ${boxNames.length} 个 Box...');
final futures = boxNames.map((name) => safeBox<dynamic>(name: name));
await Future.wait(futures);
Log.i('[HiveSafe] 所有 Box 预打开完成');
}
// ============================================================
// 管理
// ============================================================
/// 关闭指定 Box
static Future<void> closeBox(String name) async {
_instance._openBoxes.remove(name);
try {
if (Hive.isBoxOpen(name)) {
await Hive.box<dynamic>(name).close();
Log.d('[HiveSafe] Box 已关闭: $name');
}
} catch (e) {
Log.w('[HiveSafe] 关闭 Box 失败 ($name): $e');
}
}
/// 关闭所有 Box 并清理状态
static Future<void> dispose() async {
final names = _instance._openBoxes.keys.toList();
for (final name in names) {
await closeBox(name);
}
_instance._openBoxes.clear();
_instance._openingBoxes.clear();
_instance._hiveInitialized = false;
Log.i('[HiveSafe] 已清理所有资源');
}
/// 获取当前已缓存的 Box 名称列表
static List<String> get openedBoxNames => _instance._openBoxes.keys.toList();
/// 检查指定 Box 是否已打开
static bool isBoxOpen(String name) =>
_instance._openBoxes.containsKey(name) || Hive.isBoxOpen(name);
}
// ============================================================
// 便捷扩展方法
// 为现有代码提供最小改动的迁移路径
// ============================================================
/// Hive 安全访问 Mixin
/// 可混入需要使用 Hive 的 Service/Provider 类
mixin HiveSafeAccessMixin {
/// 获取指定类型的 Box带缓存和重试
Future<Box<T>> hiveBox<T>(String name) => HiveSafeAccess.safeBox<T>(name: name);
/// 尝试同步获取 Box可能返回 null
Box<T>? tryHiveBox<T>(String name) => HiveSafeAccess.tryGetBox<T>(name);
}

View File

@@ -12,6 +12,7 @@ import 'package:hive_flutter/hive_flutter.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../utils/logger.dart';
import 'hive_safe_access.dart';
// ============================================================
// Box 命名空间常量
@@ -172,23 +173,23 @@ class KvStorage {
static bool get isReady => _initialized;
// ============================================================
// 初始化
// 初始化(使用 HiveSafeAccess 统一守卫)
// ============================================================
static Future<void> init() async {
if (_initialized) return;
try {
await Hive.initFlutter();
// 通过 HiveSafeAccess 确保 Hive 初始化
await HiveSafeAccess.ensureInitialized();
for (final name in HiveBoxNames.all) {
await Hive.openBox<dynamic>(name);
}
// 使用安全访问批量预打开所有 Box
await HiveSafeAccess.preOpenBoxes(HiveBoxNames.all);
await _migrateFromSharedPreferences();
_initialized = true;
Log.i('KvStorage (Hive) 初始化完成');
Log.i('KvStorage (Hive) 初始化完成 (通过 HiveSafeAccess)');
} catch (e) {
Log.e('KvStorage (Hive) 初始化失败', e);
rethrow;
@@ -237,7 +238,7 @@ class KvStorage {
}
// ============================================================
// 内部 Box 访问
// 内部 Box 访问(优先从 HiveSafeAccess 缓存获取)
// ============================================================
static Box<dynamic>? _box(String boxName) {
@@ -245,7 +246,16 @@ class KvStorage {
Log.w('KvStorage 未初始化,跳过访问 $boxName');
return null;
}
return Hive.box<dynamic>(boxName);
// 优先从 HiveSafeAccess 缓存获取
final cached = HiveSafeAccess.tryGetBox<dynamic>(boxName);
if (cached != null) return cached as Box<dynamic>;
// fallback: 直接访问(兼容旧逻辑)
try {
return Hive.box<dynamic>(boxName);
} catch (e) {
Log.w('KvStorage 访问 Box 失败 ($boxName): $e');
return null;
}
}
// ============================================================