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

50 KiB
Raw Blame History

闲言APP 架构重构规格书

创建时间: 2026-05-24 版本: v1.0 作用: 描述当前架构三大核心问题及其解决方案,供 AI 开发者参考实施 适用范围: lib/core/lib/features/ 全部模块


目录


一、总览

当前项目存在三大架构级问题,彼此关联、相互影响:

┌─────────────────────────────────────────────────────────┐
│                    架构问题关系图                          │
│                                                         │
│  问题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 实例

核心冲突: KvStorageAppKVStore 功能高度重叠:

// 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 上降级为 SharedPreferencesKvStorage 底层冲突

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 迁移映射表

AppKVStoreKvStorage 方法映射

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

    // 改造后的 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. 合并 StorageKeysHiveBoxNames

    • StorageKeys 保留,作为所有键名的统一常量类
    • HiveBoxNames 保留,作为所有 Box 名的统一常量类

Phase 2: 逐文件替换 AppKVStore 调用2-3天

  1. 全局搜索替换 AppKVStore.getStringKvStorage.getString
  2. 逐文件验证,确保编译通过
  3. 处理返回值差异: AppKVStore.setString 返回 Future<void>KvStorage.setString 返回 Future<bool>
    • 大部分调用不使用返回值,无需修改
    • 少数检查返回值的需适配

Phase 3: 数据迁移1天

  1. 启动时自动迁移: 在 KvStorage.init() 中检测旧 key自动迁移到新 key

    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 工厂单例 (散布于各处) 多种实例化方式混用

典型问题代码:

// 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(...);
  }
}
// WidgetNotifier — 直接引用单例,紧耦合
class WidgetNotifier extends Notifier<WidgetState> {
  Future<void> addWidget(WidgetType type) async {
    final service = HomeWidgetService.instance; // 紧耦合!
    await service.init();
    await service.updateWidget(type);
  }
}

问题影响:

  • 无法单元测试: 静态方法无法 mock测试时必须依赖真实实现
  • 无法替换依赖: LocalAuthenticationAudioPlayer 等硬编码在服务内部
  • 状态管理混乱: 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 (全静态):

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):

// 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:

class SoundService {
  SoundService._();
  static final AudioPlayer _player = AudioPlayer();
  static bool _enabled = true;

  static Future<void> playClick() => _play('click');
}

After:

@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 (紧耦合):

class WidgetNotifier extends Notifier<WidgetState> {
  Future<void> addWidget(WidgetType type) async {
    final service = HomeWidgetService.instance; // 紧耦合!
    await service.init();
    await service.updateWidget(type);
  }
}

After (依赖注入):

// 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 初始化顺序 🟡 确保 ProviderScopemain() 中最早创建
生物识别平台差异 🟡 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 直接引用单例

典型问题代码:

// 问题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
  • 调试困难: 不知道状态变更的来源和传播路径
  • 内存泄漏: ValueNotifierChangeNotifier 需要手动 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 StateNotifierNotifier

当前 目标 涉及文件
AppLockService._lockNotifier appLockProviderstate.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() 注入:

当前 目标 涉及文件
WidgetNotifierHomeWidgetService.instance ref.read(homeWidgetServiceProvider) widget_provider.dart
CharacterMoodNotifierAppKVStore.xxx ref.read(kvStorageProvider) character_mood_provider.dart
SearchProviderAppKVStore.getSearchHistory() ref.read(searchHistoryServiceProvider) search_provider.dart

4.4 代码示例

示例1: ValueNotifier → Riverpod Notifier

Before:

// 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:

// 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:

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:

@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:

class WidgetNotifier extends Notifier<WidgetState> {
  Future<void> addWidget(WidgetType type) async {
    final service = HomeWidgetService.instance; // 紧耦合
    await service.init();
    await service.updateWidget(type);
  }
}

After:

// 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/ 目录。