feat: 多模块功能更新 - 文件传输/多语言/NFC/首页组件/进度美化等
- 文件传输: 设备发现、LAN发现服务优化 - NFC分享: provider和service增强 - 多语言: 16种语言翻译补全 - 首页: 句子详情面板、收藏页、离线页优化 - 我的: 成就、个人资料、签到、设置页面更新 - 新增: AR视图、进度美化页、Hive安全访问、鸿蒙兼容助手、共享组件 - iOS Widget: Intents扩展、XianyanWidget更新 - 鸿蒙: 6个卡片页面更新 - 其他: 路由注册、缓存配置、崩溃监控、TTS播放器等
This commit is contained in:
244
lib/core/storage/hive_safe_access.dart
Normal file
244
lib/core/storage/hive_safe_access.dart
Normal 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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
|
||||
Reference in New Issue
Block a user