# 闲言APP 架构重构规格书 > 创建时间: 2026-05-24 > 版本: v1.0 > 作用: 描述当前架构三大核心问题及其解决方案,供 AI 开发者参考实施 > 适用范围: `lib/core/` 和 `lib/features/` 全部模块 --- ## 目录 - [一、总览](#一总览) - [二、问题1: 状态管理碎片化](#二问题1-状态管理碎片化) - [2.1 现状分析](#21-现状分析) - [2.2 目标架构](#22-目标架构) - [2.3 迁移映射表](#23-迁移映射表) - [2.4 迁移步骤](#24-迁移步骤) - [2.5 受影响文件清单](#25-受影响文件清单) - [2.6 风险评估](#26-风险评估) - [三、问题2: 服务层全是静态方法](#三问题2-服务层全是静态方法) - [3.1 现状分析](#31-现状分析) - [3.2 目标架构](#32-目标架构) - [3.3 迁移步骤](#33-迁移步骤) - [3.4 代码示例](#34-代码示例) - [3.5 受影响文件清单](#35-受影响文件清单) - [3.6 风险评估](#36-风险评估) - [四、问题3: Provider 与 Service 职责不清](#四问题3-provider-与-service-职责不清) - [4.1 现状分析](#41-现状分析) - [4.2 目标架构](#42-目标架构) - [4.3 迁移步骤](#43-迁移步骤) - [4.4 代码示例](#44-代码示例) - [4.5 受影响文件清单](#45-受影响文件清单) - [4.6 风险评估](#46-风险评估) - [五、实施优先级与时间线](#五实施优先级与时间线) - [六、通用迁移检查清单](#六通用迁移检查清单) --- ## 一、总览 当前项目存在三大架构级问题,彼此关联、相互影响: ``` ┌─────────────────────────────────────────────────────────┐ │ 架构问题关系图 │ │ │ │ 问题1: 存储碎片化 ──────→ 问题2: 服务静态化 ──────→ 问题3: 职责不清 │ │ (5种存储方案) (无法注入/测试) (3种通知模式混用) │ │ │ │ 解决: 统一3层存储 解决: Riverpod Provider 解决: 分层架构 │ │ KvStorage/Secure/DB Service→Provider模式 UI/State/Service │ └─────────────────────────────────────────────────────────┘ ``` **推荐实施顺序**: 问题1 → 问题2 → 问题3 原因:存储层是基础设施,服务层依赖存储层,状态层依赖服务层。自底向上重构,避免循环依赖。 --- ## 二、问题1: 状态管理碎片化 ### 2.1 现状分析 项目同时存在 **5 种存储方案**,功能高度重叠: | 存储方案 | 底层实现 | 文件路径 | 用途 | |---------|---------|---------|------| | `KvStorage` | SharedPreferences | `lib/core/storage/kv_storage.dart` | 通用KV(主题/语言/引导页等) | | `AppKVStore` | Hive | `lib/core/storage/app_kv_store.dart` | 通用KV(搜索历史/工具统计/用户偏好等) | | `SecureStorage` | flutter_secure_storage | `lib/core/storage/secure_storage.dart` | 敏感数据(Token/密码) | | `Hive` 直接使用 | Hive box | 散布于各 service | 部分服务直接操作 Hive box | | `SharedPreferences` 直接使用 | SharedPreferences | 散布于各 service | 部分服务直接获取 SP 实例 | **核心冲突**: `KvStorage` 和 `AppKVStore` 功能高度重叠: ```dart // KvStorage (基于 SharedPreferences) KvStorage.getString(key) KvStorage.setString(key, value) KvStorage.getBool(key) KvStorage.setBool(key, value) KvStorage.getInt(key) KvStorage.setInt(key, value) // AppKVStore (基于 Hive) — 方法签名几乎完全一致 AppKVStore.getString(key) AppKVStore.setString(key, value) AppKVStore.getBool(key) AppKVStore.setBool(key, value) AppKVStore.getInt(key) AppKVStore.setInt(key, value) ``` **问题影响**: - 新开发者不知道该用哪个,随意选择导致数据分散 - `AppKVStore` 内部已有 `_migrateFromSharedPreferences()` 迁移逻辑,说明历史遗留问题 - 同一功能的数据可能分散在两个存储中,查询时需要检查两处 - `SecureStorage` 在 macOS 上降级为 SharedPreferences,与 `KvStorage` 底层冲突 ### 2.2 目标架构 统一为 **3 层存储架构**,每层职责明确、互不重叠: ``` ┌──────────────────────────────────────────────────┐ │ 存储架构 (3层) │ ├──────────────────────────────────────────────────┤ │ │ │ Layer 1: KvStorage (通用KV) │ │ ┌────────────────────────────────────────────┐ │ │ │ 底层: Hive │ │ │ │ 用途: 主题/语言/设置/偏好/缓存/统计 │ │ │ │ 特点: 高性能、支持多Box、同步读取 │ │ │ │ 替换: AppKVStore + KvStorage(旧SP) → 统一 │ │ │ └────────────────────────────────────────────┘ │ │ │ │ Layer 2: SecureStorage (敏感数据) │ │ ┌────────────────────────────────────────────┐ │ │ │ 底层: flutter_secure_storage │ │ │ │ 用途: Token/密码/密钥/隐私数据 │ │ │ │ 特点: iOS Keychain / Android EncryptedSP │ │ │ │ 保持: 现有实现不变 │ │ │ └────────────────────────────────────────────┘ │ │ │ │ Layer 3: Database (结构化数据) │ │ ┌────────────────────────────────────────────┐ │ │ │ 底层: Drift (SQLite) │ │ │ │ 用途: 聊天记录/收藏列表/离线队列 │ │ │ │ 特点: 关系查询、事务、复杂条件筛选 │ │ │ │ 状态: 待建设(当前用Hive box替代) │ │ │ └────────────────────────────────────────────┘ │ │ │ └──────────────────────────────────────────────────┘ ``` **关键决策**: 统一 `KvStorage` 底层从 SharedPreferences 迁移到 Hive 理由: 1. Hive 性能优于 SharedPreferences(内存级读取 vs 磁盘IO) 2. Hive 支持同步读取,SharedPreferences 只有异步 3. `AppKVStore` 已有 SP→Hive 迁移逻辑,可复用 4. Hive 支持多 Box 分区,比 SP 的扁平命名空间更清晰 ### 2.3 迁移映射表 #### `AppKVStore` → `KvStorage` 方法映射 | AppKVStore 方法 | KvStorage 新方法 | 说明 | |----------------|-----------------|------| | `AppKVStore.getString(key)` | `KvStorage.getString(key)` | 签名一致 | | `AppKVStore.setString(key, value)` | `KvStorage.setString(key, value)` | 返回值从 `Future` 改为 `Future` | | `AppKVStore.getInt(key)` | `KvStorage.getInt(key)` | 签名一致 | | `AppKVStore.setInt(key, value)` | `KvStorage.setInt(key, value)` | 返回值从 `Future` 改为 `Future` | | `AppKVStore.getBool(key)` | `KvStorage.getBool(key)` | 签名一致 | | `AppKVStore.setBool(key, value)` | `KvStorage.setBool(key, value)` | 返回值从 `Future` 改为 `Future` | | `AppKVStore.getDouble(key)` | `KvStorage.getDouble(key)` | 需新增 | | `AppKVStore.setDouble(key, value)` | `KvStorage.setDouble(key, value)` | 需新增 | | `AppKVStore.getStringList(key)` | `KvStorage.getStringList(key)` | 签名一致 | | `AppKVStore.setStringList(key, value)` | `KvStorage.setStringList(key, value)` | 签名一致 | | `AppKVStore.remove(key)` | `KvStorage.remove(key)` | 签名一致 | | `AppKVStore.containsKey(key)` | `KvStorage.containsKey(key)` | 签名一致 | | `AppKVStore.getSearchHistory()` | `KvStorage.getSearchHistory()` | 需新增便捷方法 | | `AppKVStore.addSearchHistory(keyword)` | `KvStorage.addSearchHistory(keyword)` | 需新增便捷方法 | | `AppKVStore.clearSearchHistory()` | `KvStorage.clearSearchHistory()` | 需新增便捷方法 | | `AppKVStore.getToolUsageStats()` | `KvStorage.getToolUsageStats()` | 需新增便捷方法 | | `AppKVStore.incrementToolUsage(id, name)` | `KvStorage.incrementToolUsage(id, name)` | 需新增便捷方法 | | `AppKVStore.getUserPref(key)` | `KvStorage.getUserPref(key)` | 需新增便捷方法 | | `AppKVStore.setUserPref(key, value)` | `KvStorage.setUserPref(key, value)` | 需新增便捷方法 | #### Box 命名空间映射 | AppKVStore Box | KvStorage 新方案 | 说明 | |---------------|-----------------|------| | `HiveBoxNames.app` | `KvStorage` 默认 box | 通用数据 | | `HiveBoxNames.searchHistory` | `KvStorage` box 参数 | 搜索历史 | | `HiveBoxNames.toolUsage` | `KvStorage` box 参数 | 工具统计 | | `HiveBoxNames.userPrefs` | `KvStorage` box 参数 | 用户偏好 | | `HiveBoxNames.feedCache` | `KvStorage` box 参数 | Feed 缓存 | | `HiveBoxNames.offlineQueue` | `KvStorage` box 参数 | 离线队列 | | `HiveBoxNames.cacheConfig` | `KvStorage` box 参数 | 缓存配置 | | `HiveBoxNames.chatMessages` | Database (Drift) | 聊天记录迁移到结构化存储 | ### 2.4 迁移步骤 #### Phase 1: 增强 KvStorage(1-2天) 1. **将 `KvStorage` 底层从 SharedPreferences 切换为 Hive** ```dart // 改造后的 KvStorage class KvStorage { KvStorage._(); static bool _initialized = false; /// 初始化 (main 中调用,替代原 KvStorage.init + AppKVStore.init) static Future init() async { if (_initialized) return; await Hive.initFlutter(); // 打开所有 Box for (final name in HiveBoxNames.all) { await Hive.openBox(name); } // 执行 SP → Hive 数据迁移 await _migrateFromSharedPreferences(); _initialized = true; } // 通用读写 — 默认 app box static String? getString(String key, {String box = HiveBoxNames.app}) => _box(box)?.get(key) as String?; static Future setString(String key, String value, {String box = HiveBoxNames.app}) => _box(box)?.put(key, value) ?? Future.value(); // ... 其他方法类似 } ``` 2. **合并 `AppKVStore` 的便捷方法到 `KvStorage`** - 搜索历史方法: `getSearchHistory()`, `addSearchHistory()`, `clearSearchHistory()` - 工具统计方法: `getToolUsageStats()`, `incrementToolUsage()`, `clearToolUsageStats()` - 用户偏好方法: `getUserPref()`, `setUserPref()`, `removeUserPref()`, `clearUserPrefs()` 3. **合并 `StorageKeys` 和 `HiveBoxNames`** - `StorageKeys` 保留,作为所有键名的统一常量类 - `HiveBoxNames` 保留,作为所有 Box 名的统一常量类 #### Phase 2: 逐文件替换 AppKVStore 调用(2-3天) 1. **全局搜索替换** `AppKVStore.getString` → `KvStorage.getString` 2. **逐文件验证**,确保编译通过 3. **处理返回值差异**: `AppKVStore.setString` 返回 `Future`,`KvStorage.setString` 返回 `Future` - 大部分调用不使用返回值,无需修改 - 少数检查返回值的需适配 #### Phase 3: 数据迁移(1天) 1. **启动时自动迁移**: 在 `KvStorage.init()` 中检测旧 key,自动迁移到新 key ```dart static Future _migrateFromSharedPreferences() async { final appBox = Hive.box(HiveBoxNames.app); final migrated = appBox.get('_kv_storage_migrated') as bool?; if (migrated == true) return; try { final prefs = await SharedPreferences.getInstance(); for (final key in prefs.getKeys()) { final value = prefs.get(key); if (value is String) { appBox.put(key, value); } else if (value is int) { appBox.put(key, value); } else if (value is double) { appBox.put(key, value); } else if (value is bool) { appBox.put(key, value); } } await appBox.put('_kv_storage_migrated', true); Log.i('SharedPreferences → Hive 数据迁移完成'); } catch (e) { Log.e('SharedPreferences → Hive 数据迁移失败', e); } } ``` 2. **版本标记**: 迁移完成后写入标记,避免重复迁移 #### Phase 4: 清理(0.5天) 1. **删除 `AppKVStore`** (`lib/core/storage/app_kv_store.dart`) 2. **删除 `main.dart` 中的 `AppKVStore.init()` 调用** 3. **更新 `CHANGELOG.md`** ### 2.5 受影响文件清单 #### 使用 `AppKVStore` 的文件(共 47 个) | # | 文件路径 | 使用方式 | |---|---------|---------| | 1 | `lib/features/weather/providers/weather_provider.dart` | 读写偏好 | | 2 | `lib/features/mine/settings/providers/theme_settings_provider.dart` | 读写设置 | | 3 | `lib/core/services/notification/local_notification_service.dart` | 读写配置 | | 4 | `lib/features/mine/settings/presentation/more_settings_page.dart` | 读取设置 | | 5 | `lib/features/tool_center/inspiration/presentation/pages/tool/hanzi_tool_page.dart` | 工具统计 | | 6 | `lib/features/note/presentation/note_list_page.dart` | 读写偏好 | | 7 | `lib/features/mine/settings/presentation/font_management_notifier.dart` | 读写设置 | | 8 | `lib/features/home/providers/home_provider.dart` | 读写缓存 | | 9 | `lib/features/home/providers/home_feed_mixin.dart` | 读写缓存 | | 10 | `lib/core/services/clipboard_monitor_service.dart` | 读写配置 | | 11 | `lib/features/auth/services/user_security_service.dart` | 读写安全配置 | | 12 | `lib/main.dart` | 初始化 | | 13 | `lib/features/mine/settings/providers/general_settings_provider.dart` | 读写设置 | | 14 | `lib/features/tool_center/statistics/providers/statistics_provider.dart` | 统计数据 | | 15 | `lib/features/home/providers/character_tips_provider.dart` | 读写偏好 | | 16 | `lib/features/tool_center/statistics/providers/user_stats_provider.dart` | 统计数据 | | 17 | `lib/features/tool_center/inspiration/providers/chat_session_provider.dart` | 聊天会话 | | 18 | `lib/core/services/notification/notification_center.dart` | 通知配置 | | 19 | `lib/core/services/notification/notification_scheduler.dart` | 调度配置 | | 20 | `lib/core/services/performance/performance_orchestrator.dart` | 性能配置 | | 21 | `lib/features/mine/settings/providers/sub/sound_settings_provider.dart` | 音效设置 | | 22 | `lib/core/storage/app_kv_store.dart` | 自身定义 | | 23 | `lib/features/mine/user_center/services/account_insights_service.dart` | 账户数据 | | 24 | `lib/features/tool_center/inspiration/services/readlater_folder_service.dart` | 稍后读 | | 25 | `lib/features/tool_center/inspiration/services/chat_migration_service.dart` | 聊天迁移 | | 26 | `lib/features/tool_center/inspiration/providers/tool_center_provider.dart` | 工具中心 | | 27 | `lib/features/tool_center/inspiration/services/readlater_tag_service.dart` | 稍后读标签 | | 28 | `lib/features/tool_center/inspiration/providers/chat_provider.dart` | 聊天 | | 29 | `lib/features/home/providers/favorite_provider.dart` | 收藏 | | 30 | `lib/features/search/providers/search_provider.dart` | 搜索历史 | | 31 | `lib/shared/widgets/display/appbar_date_display.dart` | 显示配置 | | 32 | `lib/core/services/notification/readlater_reminder_service.dart` | 提醒配置 | | 33 | `lib/core/services/readlater/readlater_sync_service.dart` | 同步配置 | | 34 | `lib/features/poetry/providers/poetry_provider.dart` | 诗词缓存 | | 35 | `lib/features/mine/settings/providers/date_display_provider.dart` | 日期配置 | | 36 | `lib/core/services/audio/sfx_service.dart` | 音效配置 | | 37 | `lib/core/services/audio/tts_service.dart` | TTS 配置 | | 38 | `lib/core/services/network/ip_location_service.dart` | IP 缓存 | | 39 | `lib/features/home/providers/character_mood_provider.dart` | 角色情绪 | | 40 | `lib/features/countdown/providers/countdown_provider.dart` | 倒计时 | | 41 | `lib/features/progress/providers/progress_provider.dart` | 进度 | | 42 | `lib/features/auth/providers/auth_provider.dart` | 认证 | | 43 | `lib/features/pomodoro/providers/pomodoro_provider.dart` | 番茄钟 | | 44 | `lib/features/source/providers/source_provider.dart` | 来源 | | 45 | `lib/core/services/smart_mode_service.dart` | 智能模式 | | 46 | `lib/core/services/data/jinrishici_sdk_service.dart` | 诗词数据 | | 47 | `lib/features/search/services/user_preference_service.dart` | 用户偏好 | #### 使用 `KvStorage` 的文件(共 24 个) | # | 文件路径 | 使用方式 | |---|---------|---------| | 1 | `lib/core/services/device/app_lock_service.dart` | 应用锁配置 | | 2 | `lib/features/mine/profile/presentation/profile_page.dart` | 偏好读取 | | 3 | `lib/features/mine/settings/presentation/more_settings_page.dart` | 设置读写 | | 4 | `lib/core/router/app_router.dart` | 引导页状态 | | 5 | `lib/core/services/data/backup_service.dart` | 备份配置 | | 6 | `lib/core/layout/ohos_app_shell.dart` | 布局配置 | | 7 | `lib/main.dart` | 初始化 | | 8 | `lib/features/mine/settings/providers/general_settings_provider.dart` | 通用设置 | | 9 | `lib/features/mine/settings/providers/sub/performance_settings_provider.dart` | 性能设置 | | 10 | `lib/core/services/catcher2_config_service.dart` | 崩溃配置 | | 11 | `lib/features/onboarding/providers/onboarding_provider.dart` | 引导页 | | 12 | `lib/features/mine/settings/providers/sub/display_settings_provider.dart` | 显示设置 | | 13 | `lib/features/mine/settings/providers/sub/developer_settings_provider.dart` | 开发者设置 | | 14 | `lib/features/mine/settings/providers/sub/sound_settings_provider.dart` | 音效设置 | | 15 | `lib/features/mine/settings/providers/sub/privacy_settings_provider.dart` | 隐私设置 | | 16 | `lib/features/mine/settings/providers/sub/network_settings_provider.dart` | 网络设置 | | 17 | `lib/features/mine/settings/providers/sub/general_fields_provider.dart` | 通用字段 | | 18 | `lib/core/storage/kv_storage.dart` | 自身定义 | | 19 | `lib/features/home/providers/daily_card_style_provider.dart` | 卡片样式 | | 20 | `lib/core/services/data/data_export_service.dart` | 数据导出 | | 21 | `lib/core/services/network/network_proxy_service.dart` | 代理配置 | | 22 | `lib/core/services/device/screen_wake_service.dart` | 屏幕常亮 | | 23 | `lib/core/services/device/battery_optimization_service.dart` | 电池优化 | | 24 | `lib/core/services/sound_service.dart` | 音效配置 | ### 2.6 风险评估 | 风险 | 等级 | 缓解措施 | |------|------|---------| | 数据迁移丢失 | 🔴 高 | 迁移前备份 SP 数据;迁移后不删除 SP 数据,仅标记已迁移 | | 返回值类型变化 | 🟡 中 | `AppKVStore.setString` 返回 `Future`,`KvStorage` 返回 `Future`,需逐文件检查 | | Hive Box 未打开 | 🟡 中 | `KvStorage.init()` 中确保所有 Box 都已打开;`_box()` 方法增加 null 保护 | | macOS SecureStorage 降级 | 🟡 中 | `SecureStorage` 在 macOS 上降级为 SP,需确保与 `KvStorage` 不冲突(key 前缀隔离) | | 并发读写 | 🟢 低 | Hive 天然支持同步读写,无并发问题 | | 编译错误 | 🟢 低 | 方法签名基本一致,全局替换后逐文件验证即可 | --- ## 三、问题2: 服务层全是静态方法 ### 3.1 现状分析 核心服务全部使用静态方法或单例模式,无法注入依赖、无法单元测试、无法模拟: | 服务 | 模式 | 文件路径 | 问题 | |------|------|---------|------| | `AppLockService` | 静态方法 + ValueNotifier | `lib/core/services/device/app_lock_service.dart` | 无法注入 `LocalAuthentication`,无法 mock 生物识别 | | `HomeWidgetService` | 单例 + 实例方法 | `lib/core/services/data/home_widget_service.dart` | `WidgetNotifier` 直接调用 `HomeWidgetService.instance`,紧耦合 | | `SoundService` | 静态方法 | `lib/core/services/sound_service.dart` | 无法注入 `AudioPlayer`,无法 mock 音效播放 | | `CrashLogService` | 单例 + 实例方法 | `lib/core/services/crash_log_service.dart` | 无法注入文件系统,无法 mock 日志存储 | | `WallpaperService` | 工厂单例 | (散布于各处) | 多种实例化方式混用 | **典型问题代码**: ```dart // AppLockService — 全静态,无法替换依赖 class AppLockService { AppLockService._(); static final LocalAuthentication _localAuth = LocalAuthentication(); // 硬编码依赖 static final _lockNotifier = ValueNotifier(false); // 自己管理状态 static bool get isEnabled => KvStorage.getBool(_keyEnabled) ?? false; // 直接读存储 static void setEnabled(bool v) { KvStorage.setBool(_keyEnabled, v); // 直接写存储 } static Future authenticateBiometric({...}) async { // 无法在测试中 mock _localAuth final didAuthenticate = await _localAuth.authenticate(...); } } ``` ```dart // WidgetNotifier — 直接引用单例,紧耦合 class WidgetNotifier extends Notifier { Future addWidget(WidgetType type) async { final service = HomeWidgetService.instance; // 紧耦合! await service.init(); await service.updateWidget(type); } } ``` **问题影响**: - **无法单元测试**: 静态方法无法 mock,测试时必须依赖真实实现 - **无法替换依赖**: `LocalAuthentication`、`AudioPlayer` 等硬编码在服务内部 - **状态管理混乱**: `AppLockService` 自己用 `ValueNotifier` 管状态,绕过了 Riverpod - **初始化顺序依赖**: 静态方法依赖 `KvStorage.init()` 先执行,否则崩溃 ### 3.2 目标架构 将服务改为 **Riverpod Provider** 模式,实现依赖注入和可测试性: ``` ┌──────────────────────────────────────────────────────────┐ │ 服务层架构 (Riverpod Provider) │ ├──────────────────────────────────────────────────────────┤ │ │ │ ┌─────────────────────────────────────────────────────┐ │ │ │ UI Layer (Widget) │ │ │ │ ref.watch(appLockProvider) │ │ │ │ ref.read(appLockProvider.notifier).authenticate() │ │ │ └──────────────────────┬──────────────────────────────┘ │ │ │ ref.watch / ref.read │ │ ┌──────────────────────▼──────────────────────────────┐ │ │ │ State Layer (Riverpod Notifier) │ │ │ │ AppLockNotifier extends _$AppLock │ │ │ │ - 持有状态 (AppLockState) │ │ │ │ - 调用 Service 方法 │ │ │ │ - 更新 state │ │ │ └──────────────────────┬──────────────────────────────┘ │ │ │ 调用 │ │ ┌──────────────────────▼──────────────────────────────┐ │ │ │ Service Layer (纯业务逻辑,可注入) │ │ │ │ AppLockService(ref) │ │ │ │ - 依赖通过 Provider 注入 │ │ │ │ - 不持有 UI 状态 │ │ │ │ - 返回结果给 Notifier │ │ │ └──────────────────────┬──────────────────────────────┘ │ │ │ 读写 │ │ ┌──────────────────────▼──────────────────────────────┐ │ │ │ Data Layer (KvStorage / SecureStorage / Database) │ │ │ └─────────────────────────────────────────────────────┘ │ │ │ └──────────────────────────────────────────────────────────┘ ``` ### 3.3 迁移步骤 #### 优先迁移顺序 1. **AppLockService** → 最复杂,有 ValueNotifier + 生物识别 + 多种解锁方式 2. **WallpaperService** → 中等复杂度 3. **HomeWidgetService** → 已有 WidgetNotifier,需解耦 4. **SoundService** → 简单,静态方法改 Provider 5. **CrashLogService** → 简单,单例改 Provider #### 通用迁移模式 每个服务的迁移遵循以下步骤: 1. **创建 State 数据类** 2. **创建 Riverpod Notifier** 3. **改造 Service 为可注入类** 4. **UI 层从 Provider 获取状态** 5. **旧静态方法标记 @deprecated** 6. **验证通过后删除旧方法** ### 3.4 代码示例 #### 示例1: AppLockService 迁移 **Before** (全静态): ```dart class AppLockService { AppLockService._(); static final LocalAuthentication _localAuth = LocalAuthentication(); static final _lockNotifier = ValueNotifier(false); static bool _isLocked = false; static ValueNotifier get lockNotifier => _lockNotifier; static bool get isLocked => _isLocked; static void lock() { _isLocked = true; _lockNotifier.value = true; } static Future authenticateBiometric({...}) async { final didAuthenticate = await _localAuth.authenticate(...); if (didAuthenticate) { unlock(); } return didAuthenticate; } } // UI 使用 ValueListenableBuilder( valueListenable: AppLockService.lockNotifier, builder: (_, isLocked, __) => isLocked ? LockScreen() : Content(), ) ``` **After** (Riverpod Provider): ```dart // 1. State 数据类 @freezed class AppLockState with _$AppLockState { const factory AppLockState({ @Default(false) bool isEnabled, @Default(AppLockMethod.none) AppLockMethod method, @Default(false) bool isLocked, @Default(false) bool isAuthenticating, @Default(0) int failedAttempts, DateTime? lockoutUntil, }) = _AppLockState; } // 2. Service 改为可注入类 class AppLockService { final LocalAuthentication _localAuth; final KvStorage _storage; final SecureStorage _secureStorage; AppLockService({ required LocalAuthentication localAuth, required KvStorage storage, required SecureStorage secureStorage, }) : _localAuth = localAuth, _storage = storage, _secureStorage = secureStorage; Future authenticateBiometric({String reason = '请验证身份以解锁闲言'}) async { final isSupported = await _localAuth.isDeviceSupported(); if (!isSupported) return false; return await _localAuth.authenticate(localizedReason: reason); } Future getStoredPattern() => _secureStorage.read(_keyPattern); Future getStoredPin() => _secureStorage.read(_keyPin); Future verifyPattern(String pattern) async { final stored = await _secureStorage.read(_keyPattern); return pattern == stored; } Future verifyPin(String pin) async { final stored = await _secureStorage.read(_keyPin); return pin == stored; } } // 3. Provider final appLockServiceProvider = Provider((ref) { return AppLockService( localAuth: LocalAuthentication(), storage: KvStorage.instance, secureStorage: SecureStorage.instance, ); }); @riverpod class AppLock extends _$AppLock { @override AppLockState build() { final service = ref.read(appLockServiceProvider); return AppLockState( isEnabled: service._storage.getBool(_keyEnabled) ?? false, method: AppLockMethod.fromId(service._storage.getString(_keyMethod) ?? 'none'), ); } Future authenticate() async { final service = ref.read(appLockServiceProvider); state = state.copyWith(isAuthenticating: true); try { bool success = false; switch (state.method) { case AppLockMethod.biometric: success = await service.authenticateBiometric(); break; case AppLockMethod.pattern: // UI 层处理手势输入,调用 verifyPattern break; case AppLockMethod.pin: // UI 层处理密码输入,调用 verifyPin break; case AppLockMethod.none: success = true; } if (success) { state = state.copyWith( isLocked: false, failedAttempts: 0, lockoutUntil: null, ); } else { final attempts = state.failedAttempts + 1; state = state.copyWith( failedAttempts: attempts, lockoutUntil: attempts >= 5 ? DateTime.now().add(const Duration(minutes: 5)) : null, ); } } finally { state = state.copyWith(isAuthenticating: false); } } void lock() { if (!state.isEnabled) return; state = state.copyWith(isLocked: true); } void unlock() { state = state.copyWith(isLocked: false, failedAttempts: 0, lockoutUntil: null); } void setEnabled(bool v) { ref.read(appLockServiceProvider)._storage.setBool(_keyEnabled, v); state = state.copyWith(isEnabled: v); if (!v) unlock(); } } // UI 使用 class AppLockGate extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final lockState = ref.watch(appLockProvider); if (lockState.isLocked) return const LockScreen(); return const Content(); } } ``` #### 示例2: SoundService 迁移 **Before**: ```dart class SoundService { SoundService._(); static final AudioPlayer _player = AudioPlayer(); static bool _enabled = true; static Future playClick() => _play('click'); } ``` **After**: ```dart @riverpod class Sound extends _$Sound { @override SoundState build() { return SoundState( enabled: KvStorage.getBool('general_sound') ?? true, effectType: SoundEffectType.fromId( KvStorage.getString('general_sound_effect') ?? 'standard', ), ); } Future playClick() => _play('click'); Future playToggle() => _play('toggle'); Future playSuccess() => _play('success'); void setEnabled(bool v) { KvStorage.setBool('general_sound', v); state = state.copyWith(enabled: v); } Future _play(String action) async { if (!state.enabled) return; try { final player = ref.read(audioPlayerProvider); await player.stop(); await player.play(AssetSource('assets/sounds/${state.effectType.id}_$action.mp3')); } catch (e) { Log.w('音效播放失败($action): $e'); } } } // AudioPlayer 作为可注入依赖 final audioPlayerProvider = Provider((ref) { final player = AudioPlayer(); ref.onDispose(() => player.dispose()); return player; }); ``` #### 示例3: HomeWidgetService 迁移 — 解耦 WidgetNotifier **Before** (紧耦合): ```dart class WidgetNotifier extends Notifier { Future addWidget(WidgetType type) async { final service = HomeWidgetService.instance; // 紧耦合! await service.init(); await service.updateWidget(type); } } ``` **After** (依赖注入): ```dart // HomeWidgetService 改为 Provider final homeWidgetServiceProvider = Provider((ref) { return HomeWidgetService(); }); // WidgetNotifier 通过 ref 获取 Service class WidgetNotifier extends Notifier { @override WidgetState build() => const WidgetState(); Future addWidget(WidgetType type) async { final service = ref.read(homeWidgetServiceProvider); // 依赖注入! await service.init(); await service.updateWidget(type); // ... } } ``` ### 3.5 受影响文件清单 | 服务 | 当前模式 | 目标模式 | 涉及文件 | |------|---------|---------|---------| | `AppLockService` | 静态方法 + ValueNotifier | Riverpod Notifier | `app_lock_service.dart`, `app_lock_overlay.dart`, `app_lock_settings_page.dart`, `app.dart`, `ohos_app_shell.dart` | | `HomeWidgetService` | 单例 | Riverpod Provider | `home_widget_service.dart`, `widget_provider.dart`, `app.dart`, `main.dart`, `home_feed_mixin.dart` | | `SoundService` | 静态方法 | Riverpod Notifier | `sound_service.dart`, `sound_settings_provider.dart` | | `CrashLogService` | 单例 | Riverpod Provider | `crash_log_service.dart`, `crash_log_page.dart`, `catcher2_config_service.dart` | | `WidgetNotifier` | Notifier + 直接引用单例 | Notifier + Provider 注入 | `widget_provider.dart` | ### 3.6 风险评估 | 风险 | 等级 | 缓解措施 | |------|------|---------| | 迁移期间功能回归 | 🔴 高 | 保留旧静态方法标记 `@deprecated`,新旧并行运行,验证后删除 | | Riverpod 初始化顺序 | 🟡 中 | 确保 `ProviderScope` 在 `main()` 中最早创建 | | 生物识别平台差异 | 🟡 中 | `LocalAuthentication` 在某些平台不支持,Provider 中需处理异常 | | freezed 代码生成 | 🟢 低 | 项目已有 freezed 依赖,运行 `dart run build_runner build` 即可 | | 性能影响 | 🟢 低 | Riverpod Provider 是懒加载的,不会增加启动时间 | --- ## 四、问题3: Provider 与 Service 职责不清 ### 4.1 现状分析 项目中存在 **3 种不同的状态通知模式**混用: | 模式 | 使用者 | 问题 | |------|-------|------| | `ValueNotifier` | `AppLockService._lockNotifier` | 服务自己管状态,绕过 Riverpod | | `ChangeNotifier` | `EditorThemeNotifier`, `CanvasEngine`, `LayerManagerService` | 编辑器模块独有,与 Riverpod 不互通 | | `Riverpod Notifier` | `WidgetNotifier`, 各种 Provider | 正确模式,但部分 Provider 直接引用单例 | **典型问题代码**: ```dart // 问题1: WidgetNotifier 直接调用 HomeWidgetService.instance class WidgetNotifier extends Notifer { Future addWidget(WidgetType type) async { final service = HomeWidgetService.instance; // 紧耦合 await service.init(); await service.updateWidget(type); } } // 问题2: AppLockService 用 ValueNotifier 通知 UI class AppLockService { static final _lockNotifier = ValueNotifier(false); static ValueNotifier get lockNotifier => _lockNotifier; // UI 需要 ValueListenableBuilder 监听,与 Riverpod 体系不一致 } // 问题3: 编辑器用 ChangeNotifier class EditorThemeNotifier extends ChangeNotifier { void updateTheme(ThemeData theme) { _theme = theme; notifyListeners(); // 手动通知,容易遗漏 } } ``` **问题影响**: - **UI 层需要适配多种监听方式**: `ref.watch` / `ValueListenableBuilder` / `ListenableBuilder` - **状态来源不统一**: 同一个功能的状态可能来自 Service/Notifier/Provider - **调试困难**: 不知道状态变更的来源和传播路径 - **内存泄漏**: `ValueNotifier` 和 `ChangeNotifier` 需要手动 dispose,容易遗漏 ### 4.2 目标架构 统一为 **4 层架构**,每层职责清晰: ``` ┌──────────────────────────────────────────────────────────────┐ │ 4 层架构 │ ├──────────────────────────────────────────────────────────────┤ │ │ │ Layer 1: UI Layer (Widget) │ │ ┌────────────────────────────────────────────────────────┐ │ │ │ 职责: 渲染界面、响应交互 │ │ │ │ 规则: │ │ │ │ - 只通过 ref.watch / ref.read 与 State Layer 交互 │ │ │ │ - 禁止直接调用 Service │ │ │ │ - 禁止直接读写 Storage │ │ │ │ - 禁止使用 ValueListenableBuilder 监听 Service │ │ │ └────────────────────────────────────────────────────────┘ │ │ │ ref.watch / ref.read │ │ Layer 2: State Layer (Riverpod Notifier) │ │ ┌────────────────────────────────────────────────────────┐ │ │ │ 职责: 持有状态、调用 Service、通知 UI │ │ │ │ 规则: │ │ │ │ - 所有 UI 状态必须通过 Notifier 持有 │ │ │ │ - 调用 Service 获取/修改数据 │ │ │ │ - Service 返回结果后更新 state │ │ │ │ - 禁止在 Notifier 中直接操作 Storage │ │ │ └────────────────────────────────────────────────────────┘ │ │ │ 调用 │ │ Layer 3: Service Layer (纯业务逻辑) │ │ ┌────────────────────────────────────────────────────────┐ │ │ │ 职责: 执行业务逻辑、读写数据 │ │ │ │ 规则: │ │ │ │ - 不持有 UI 状态 (无 ValueNotifier/ChangeNotifier) │ │ │ │ - 不直接通知 UI │ │ │ │ - 返回结果给 Notifier,由 Notifier 决定如何更新状态 │ │ │ │ - 通过 Provider 注入依赖 │ │ │ └────────────────────────────────────────────────────────┘ │ │ │ 读写 │ │ Layer 4: Data Layer (KvStorage / SecureStorage / Database) │ │ ┌────────────────────────────────────────────────────────┐ │ │ │ 职责: 数据持久化 │ │ │ │ 规则: │ │ │ │ - 只有 Service 可以调用 │ │ │ │ - Notifier 和 UI 禁止直接调用 │ │ │ └────────────────────────────────────────────────────────┘ │ │ │ └──────────────────────────────────────────────────────────────┘ ``` **数据流方向**: ``` UI → ref.read(notifier.action) → Notifier.action() → Service.doSomething() → Storage.read/write ← Service 返回结果 ← Notifier 更新 state ← UI 通过 ref.watch 自动重建 ``` ### 4.3 迁移步骤 #### Step 1: 消除 ValueNotifier(1-2天) 将所有 `ValueNotifier` 改为 Riverpod `StateNotifier` 或 `Notifier`: | 当前 | 目标 | 涉及文件 | |------|------|---------| | `AppLockService._lockNotifier` | `appLockProvider` 的 `state.isLocked` | `app_lock_service.dart`, `app_lock_overlay.dart`, `app.dart` | | `ShaderCardBackground._notifier` | 保留(纯渲染层,非业务状态) | `shader_card_background.dart` | | `AppBarCharacterSprite._notifier` | 保留(纯渲染层,非业务状态) | `appbar_character_sprite.dart` | **判断标准**: 纯渲染层的 `ValueNotifier`(如动画、Canvas 重绘)可以保留,因为它们不属于业务状态。业务状态(如锁定/解锁、设置变更)必须迁移到 Riverpod。 #### Step 2: 消除 ChangeNotifier(2-3天) 将所有 `ChangeNotifier` 改为 Riverpod `AsyncNotifier`: | 当前 | 目标 | 涉及文件 | |------|------|---------| | `EditorThemeNotifier` | `editorThemeProvider` | `editor_theme_notifier.dart`, `editor_theme_service.dart` | | `CanvasEngine` | `canvasProvider` | `canvas_engine.dart` | | `LayerManagerService` | `layerManagerProvider` | `layer_manager_service.dart` | | `Scene3DService` | `scene3DProvider` | `scene_3d_service.dart` | | `SheetAnimationNotifier` | 保留(纯 UI 动画) | `sheet_animation_notifier.dart` | **注意**: 编辑器模块的 `ChangeNotifier` 较复杂,建议在编辑器模块整体重构时一并处理,不要单独迁移。 #### Step 3: 解耦 Provider 与 Service(1-2天) 将 Provider 中直接引用 Service 单例的代码改为通过 `ref.read()` 注入: | 当前 | 目标 | 涉及文件 | |------|------|---------| | `WidgetNotifier` → `HomeWidgetService.instance` | `ref.read(homeWidgetServiceProvider)` | `widget_provider.dart` | | `CharacterMoodNotifier` → `AppKVStore.xxx` | `ref.read(kvStorageProvider)` | `character_mood_provider.dart` | | `SearchProvider` → `AppKVStore.getSearchHistory()` | `ref.read(searchHistoryServiceProvider)` | `search_provider.dart` | ### 4.4 代码示例 #### 示例1: ValueNotifier → Riverpod Notifier **Before**: ```dart // Service 自己管状态 class AppLockService { static final _lockNotifier = ValueNotifier(false); static ValueNotifier get lockNotifier => _lockNotifier; static void lock() { _isLocked = true; _lockNotifier.value = true; // 直接通知 UI } } // UI 用 ValueListenableBuilder 监听 ValueListenableBuilder( valueListenable: AppLockService.lockNotifier, builder: (_, isLocked, __) { if (isLocked) return const LockScreen(); return const Content(); }, ) ``` **After**: ```dart // Notifier 管状态,Service 只做业务逻辑 @riverpod class AppLock extends _$AppLock { @override AppLockState build() => AppLockState.initial(); void lock() { if (!state.isEnabled) return; state = state.copyWith(isLocked: true); // Service 不需要知道 UI 状态 } } // UI 用 ref.watch 监听 class AppLockGate extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final isLocked = ref.watch(appLockProvider.select((s) => s.isLocked)); if (isLocked) return const LockScreen(); return const Content(); } } ``` #### 示例2: ChangeNotifier → Riverpod AsyncNotifier **Before**: ```dart class EditorThemeNotifier extends ChangeNotifier { ThemeData _theme = ThemeData.light(); ThemeData get theme => _theme; void updateTheme(ThemeData theme) { _theme = theme; notifyListeners(); // 手动通知 } } // UI ListenableBuilder( listenable: editorThemeNotifier, builder: (_, __) => ThemedContent(theme: editorThemeNotifier.theme), ) ``` **After**: ```dart @riverpod class EditorTheme extends _$EditorTheme { @override Future build() async { final service = ref.read(editorThemeServiceProvider); return service.loadTheme(); } Future updateTheme(ThemeData theme) async { final service = ref.read(editorThemeServiceProvider); await service.saveTheme(theme); state = AsyncData(theme); } } // UI class ThemedContentWrapper extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final themeAsync = ref.watch(editorThemeProvider); return themeAsync.when( data: (theme) => ThemedContent(theme: theme), loading: () => const CircularProgressIndicator(), error: (e, _) => Text('Error: $e'), ); } } ``` #### 示例3: 解耦 Provider 与 Service **Before**: ```dart class WidgetNotifier extends Notifier { Future addWidget(WidgetType type) async { final service = HomeWidgetService.instance; // 紧耦合 await service.init(); await service.updateWidget(type); } } ``` **After**: ```dart // Service 注册为 Provider final homeWidgetServiceProvider = Provider((ref) { return HomeWidgetService(); }); // Notifier 通过 ref 获取 Service class WidgetNotifier extends Notifier { @override WidgetState build() => const WidgetState(); Future addWidget(WidgetType type) async { final service = ref.read(homeWidgetServiceProvider); // 依赖注入 await service.init(); await service.updateWidget(type); state = state.copyWith(installedWidgets: {...state.installedWidgets, type}); } } ``` ### 4.5 受影响文件清单 | 模式 | 文件 | 迁移目标 | |------|------|---------| | ValueNotifier | `lib/core/services/device/app_lock_service.dart` | Riverpod Notifier | | ValueNotifier | `lib/shared/widgets/animation/shader_card_background.dart` | 保留(纯渲染) | | ValueNotifier | `lib/shared/widgets/animation/appbar_character_sprite.dart` | 保留(纯渲染) | | ChangeNotifier | `lib/editor/services/core/editor_theme_notifier.dart` | Riverpod AsyncNotifier | | ChangeNotifier | `lib/features/file_transfer/collaboration/canvas/services/canvas_engine.dart` | Riverpod Provider | | ChangeNotifier | `lib/editor/services/core/layer_manager_service.dart` | Riverpod Provider | | ChangeNotifier | `lib/editor/services/3d/scene_3d_service.dart` | Riverpod Provider | | ChangeNotifier | `lib/core/utils/ui/sheet_animation_notifier.dart` | 保留(纯 UI 动画) | | 紧耦合 | `lib/features/widget/providers/widget_provider.dart` | 解耦 Service | | 直接读存储 | `lib/features/home/providers/character_mood_provider.dart` | 通过 Service | | 直接读存储 | `lib/features/search/providers/search_provider.dart` | 通过 Service | ### 4.6 风险评估 | 风险 | 等级 | 缓解措施 | |------|------|---------| | 编辑器模块 ChangeNotifier 复杂 | 🔴 高 | 编辑器模块单独重构,不在此轮迁移 | | ValueNotifier → Notifier 行为差异 | 🟡 中 | `ValueNotifier` 是同步通知,`Notifier` 也是同步更新 state,行为一致 | | ChangeNotifier → AsyncNotifier 异步化 | 🟡 中 | 部分 ChangeNotifier 是同步的,改为 AsyncNotifier 后 UI 需要处理 loading 状态 | | 纯渲染 ValueNotifier 误迁移 | 🟡 中 | 严格区分业务状态和渲染状态,纯渲染层保留 ValueNotifier | | ref.read 时序问题 | 🟢 低 | 确保 `ref.read` 在回调中使用,不在 `build` 中使用 | --- ## 五、实施优先级与时间线 ``` Week 1-2: 问题1 — 存储碎片化 ├── Day 1-2: Phase 1 — 增强 KvStorage ├── Day 3-5: Phase 2 — 逐文件替换 AppKVStore ├── Day 6: Phase 3 — 数据迁移 └── Day 7: Phase 4 — 清理 + 验证 Week 3-4: 问题2 — 服务静态化 ├── Day 1-3: AppLockService → Riverpod Provider ├── Day 4-5: HomeWidgetService → 解耦 ├── Day 6: SoundService → Riverpod Notifier └── Day 7: CrashLogService → Riverpod Provider Week 5-6: 问题3 — 职责不清 ├── Day 1-2: 消除 ValueNotifier (AppLockService) ├── Day 3-5: 消除 ChangeNotifier (编辑器除外) └── Day 6-7: 解耦 Provider 与 Service + 全面验证 ``` **总预估**: 6 周(含测试和验证) --- ## 六、通用迁移检查清单 每个模块迁移完成后,需逐项检查: ### 编译检查 - [ ] `dart analyze` 无错误 - [ ] `flutter build` 成功 - [ ] 无 `@deprecated` 警告(已清理旧代码) ### 功能检查 - [ ] 旧功能行为不变 - [ ] 数据迁移正确(旧数据可读、新数据可写) - [ ] 前后台切换无崩溃 - [ ] 鸿蒙端兼容(`TargetPlatform.ohos` 处理) ### 架构检查 - [ ] UI 层不直接调用 Service - [ ] UI 层不直接读写 Storage - [ ] Service 不持有 UI 状态(无 ValueNotifier/ChangeNotifier) - [ ] 所有依赖通过 Provider 注入 - [ ] 无静态方法直接引用(已迁移到 Provider) ### 测试检查 - [ ] Service 可 mock(依赖注入) - [ ] Notifier 可单元测试 - [ ] 数据迁移有回退机制 ### 文档检查 - [ ] `CHANGELOG.md` 已更新 - [ ] 文件头部注释已更新 - [ ] 本文档已标记完成状态 --- > **文档维护说明**: 每完成一个迁移步骤,请在此文档对应位置标记 ✅。全部完成后,将此文档归档到 `docs/completed/` 目录。