Files
xianyan/docs/architecture-refactor-spec.md
Developer df1f127a12 chore: 批量完成2026-05-24版本迭代更新
本次提交涵盖多项功能优化与重构:
1. 重构pro_image_editor依赖为官方托管版本,移除本地包引用
2. 拆分角色表情枚举至独立文件,优化代码复用性
3. 新增壁纸收藏、预加载、健康检测服务与本地存储支持
4. 完善API响应类型安全检查与排行榜服务能力
5. 新增应用锁设置路由与页面支持
6. 优化路由跳转使用常量路径替代硬编码字符串
7. 新增阅读报告分享功能与设置变更日志服务
8. 修复多处类型转换与空指针风险问题
9. 调整API超时时间优化网络请求表现
10. 统一文件头格式与部分UI组件样式
2026-05-24 04:00:49 +08:00

1149 lines
50 KiB
Markdown
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-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<void>` 改为 `Future<bool>` |
| `AppKVStore.getInt(key)` | `KvStorage.getInt(key)` | 签名一致 |
| `AppKVStore.setInt(key, value)` | `KvStorage.setInt(key, value)` | 返回值从 `Future<void>` 改为 `Future<bool>` |
| `AppKVStore.getBool(key)` | `KvStorage.getBool(key)` | 签名一致 |
| `AppKVStore.setBool(key, value)` | `KvStorage.setBool(key, value)` | 返回值从 `Future<void>` 改为 `Future<bool>` |
| `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: 增强 KvStorage1-2天
1. **将 `KvStorage` 底层从 SharedPreferences 切换为 Hive**
```dart
// 改造后的 KvStorage
class KvStorage {
KvStorage._();
static bool _initialized = false;
/// 初始化 (main 中调用,替代原 KvStorage.init + AppKVStore.init)
static Future<void> init() async {
if (_initialized) return;
await Hive.initFlutter();
// 打开所有 Box
for (final name in HiveBoxNames.all) {
await Hive.openBox<dynamic>(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<void> 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<void>``KvStorage.setString` 返回 `Future<bool>`
- 大部分调用不使用返回值,无需修改
- 少数检查返回值的需适配
#### Phase 3: 数据迁移1天
1. **启动时自动迁移**: 在 `KvStorage.init()` 中检测旧 key自动迁移到新 key
```dart
static Future<void> _migrateFromSharedPreferences() async {
final appBox = Hive.box<dynamic>(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<void>``KvStorage` 返回 `Future<bool>`,需逐文件检查 |
| 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<bool>(false); // 自己管理状态
static bool get isEnabled => KvStorage.getBool(_keyEnabled) ?? false; // 直接读存储
static void setEnabled(bool v) {
KvStorage.setBool(_keyEnabled, v); // 直接写存储
}
static Future<bool> authenticateBiometric({...}) async {
// 无法在测试中 mock _localAuth
final didAuthenticate = await _localAuth.authenticate(...);
}
}
```
```dart
// WidgetNotifier — 直接引用单例,紧耦合
class WidgetNotifier extends Notifier<WidgetState> {
Future<void> 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<bool>(false);
static bool _isLocked = false;
static ValueNotifier<bool> get lockNotifier => _lockNotifier;
static bool get isLocked => _isLocked;
static void lock() {
_isLocked = true;
_lockNotifier.value = true;
}
static Future<bool> authenticateBiometric({...}) async {
final didAuthenticate = await _localAuth.authenticate(...);
if (didAuthenticate) { unlock(); }
return didAuthenticate;
}
}
// UI 使用
ValueListenableBuilder<bool>(
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<bool> authenticateBiometric({String reason = '请验证身份以解锁闲言'}) async {
final isSupported = await _localAuth.isDeviceSupported();
if (!isSupported) return false;
return await _localAuth.authenticate(localizedReason: reason);
}
Future<String?> getStoredPattern() => _secureStorage.read(_keyPattern);
Future<String?> getStoredPin() => _secureStorage.read(_keyPin);
Future<bool> verifyPattern(String pattern) async {
final stored = await _secureStorage.read(_keyPattern);
return pattern == stored;
}
Future<bool> verifyPin(String pin) async {
final stored = await _secureStorage.read(_keyPin);
return pin == stored;
}
}
// 3. Provider
final appLockServiceProvider = Provider<AppLockService>((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<void> 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<void> 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<void> playClick() => _play('click');
Future<void> playToggle() => _play('toggle');
Future<void> playSuccess() => _play('success');
void setEnabled(bool v) {
KvStorage.setBool('general_sound', v);
state = state.copyWith(enabled: v);
}
Future<void> _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<AudioPlayer>((ref) {
final player = AudioPlayer();
ref.onDispose(() => player.dispose());
return player;
});
```
#### 示例3: HomeWidgetService 迁移 — 解耦 WidgetNotifier
**Before** (紧耦合):
```dart
class WidgetNotifier extends Notifier<WidgetState> {
Future<void> addWidget(WidgetType type) async {
final service = HomeWidgetService.instance; // 紧耦合!
await service.init();
await service.updateWidget(type);
}
}
```
**After** (依赖注入):
```dart
// HomeWidgetService 改为 Provider
final homeWidgetServiceProvider = Provider<HomeWidgetService>((ref) {
return HomeWidgetService();
});
// WidgetNotifier 通过 ref 获取 Service
class WidgetNotifier extends Notifier<WidgetState> {
@override
WidgetState build() => const WidgetState();
Future<void> 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<WidgetState> {
Future<void> 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<bool>(false);
static ValueNotifier<bool> 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: 消除 ValueNotifier1-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: 消除 ChangeNotifier2-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 与 Service1-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<bool>(false);
static ValueNotifier<bool> get lockNotifier => _lockNotifier;
static void lock() {
_isLocked = true;
_lockNotifier.value = true; // 直接通知 UI
}
}
// UI 用 ValueListenableBuilder 监听
ValueListenableBuilder<bool>(
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<ThemeData> build() async {
final service = ref.read(editorThemeServiceProvider);
return service.loadTheme();
}
Future<void> 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<WidgetState> {
Future<void> addWidget(WidgetType type) async {
final service = HomeWidgetService.instance; // 紧耦合
await service.init();
await service.updateWidget(type);
}
}
```
**After**:
```dart
// Service 注册为 Provider
final homeWidgetServiceProvider = Provider<HomeWidgetService>((ref) {
return HomeWidgetService();
});
// Notifier 通过 ref 获取 Service
class WidgetNotifier extends Notifier<WidgetState> {
@override
WidgetState build() => const WidgetState();
Future<void> 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/` 目录。