January 1, 2026
7 min read
By devshan

Table of Contents

This is a list of all the sections in this post. Click on any of them to jump to that section.

状态: 完成
影响范围: Flutter 客户端代码


1. 背景与目标

1.1 问题分析

在之前的代码评测中,发现了以下主要问题:

问题描述严重程度
God Class 反模式AppState 3466 行,职责过多
魔法数字散落配置值硬编码在各处
无 LRU 缓存缩略图缓存无容量限制,可能内存泄漏
debugPrint 滥用25+ 处使用 debugPrint,无统一日志管理
冗余 import17 处不必要的 import
SharedPreferences 分散键名散落各处,易拼写错误

1.2 优化目标

  1. 代码仓库结构的进一步优化 - 创建清晰的目录结构
  2. 在不影响功能的情况下优化业务逻辑 - 提供更好的使用体验
  3. 修复明显 bug 和不合理的地方 - 代码质量提升

关键约束: 仅优化代码结构,不改变任何业务逻辑和功能


2. 方案设计

2.1 分阶段优化策略

经过分析,决定采用分阶段、低风险的优化策略:

Phase 1: 基础设施 - 创建常量和服务层
Phase 2: 延后 - AppState 拆分风险太高
Phase 3: 缓存优化 - 创建 LRU 缓存
Phase 4: 日志系统 - 统一日志管理
Phase 5: 代码清理 - 清理冗余代码
Phase 6: 集成应用 - 将新组件应用到现有代码

2.2 决策取舍

取舍 1: AppState 拆分延后

原计划: 将 AppState 拆分为多个 Provider(AuthProvider, FileProvider, TransferProvider, CoreProvider)

发现的问题:

  • context.read<AppState>() 在代码中有 50+ 处引用
  • 拆分需要修改大量文件,风险高
  • 可能引入难以调试的状态同步问题

最终决策: 延后到独立任务,本次仅创建基础设施

取舍 2: ThumbnailCacheAdapter vs 直接修改组件

选项 A: 创建 ThumbnailCacheAdapter 实现 Map 接口

  • 优点: 现有代码无需修改
  • 缺点: 需要实现完整的 Map 接口,复杂

选项 B: 直接修改使用缓存的组件

  • 优点: 代码更简洁
  • 缺点: 需要修改多处代码

最终决策: 采用混合方案

  • chat_page.dartimage_preview_page.dart 直接使用 ThumbnailMemoryCache
  • files_page.dart 保持 Map 接口(因为要传递给子组件,修改成本高)

取舍 3: 单例 vs 实例化

对于 ThumbnailMemoryCache:

选项 A: 全局单例

  • 优点: 所有页面共享缓存,内存效率高
  • 缺点: 全局状态,测试困难

选项 B: 每个页面独立实例

  • 优点: 隔离性好,易于测试
  • 缺点: 不同页面缓存不共享

最终决策: 采用全局单例模式

  • 缩略图是静态资源,跨页面共享有意义
  • 单例内部使用 LRU,自动管理容量

3. 实现细节

3.1 Phase 1: 常量管理

3.1.1 创建目录结构

lib/core/
├── constants/ # 新建
│ ├── app_constants.dart
│ └── storage_keys.dart
├── services/
│ └── storage_service.dart # 新建
└── utils/
├── thumbnail_memory_cache.dart # 新建
├── simple_lru_cache.dart # 新建
└── app_logger.dart # 新建

3.1.2 AppConstants 设计

class AppConstants {
  AppConstants._();  // 私有构造函数,防止实例化
 
  // ===================== 分页 =====================
  static const int messagePageSize = 50;
  static const int filePageSize = 100;
 
  // ===================== 缓存 =====================
  static const int thumbnailMemoryCacheMaxSize = 100;
  static const int imagePreviewCacheMaxSize = 10;
 
  // ===================== 并发 =====================
  static const int defaultUploadConcurrency = 3;
  static const int defaultDownloadConcurrency = 3;
 
  // ===================== 超时 =====================
  static const Duration apiConnectTimeout = Duration(seconds: 3);
  static const Duration healthCheckInterval = Duration(seconds: 15);
  // ...
}

设计要点:

  • 使用私有构造函数防止实例化
  • 按功能分组,便于查找
  • 使用 static const,编译时常量

3.1.3 StorageKeys 设计

class StorageKeys {
  StorageKeys._();
 
  // 核心配置
  static const String coreMode = 'core_mode';
  static const String coreBaseUrl = 'core_base_url';
 
  // 动态键名生成
  static String videoPosition(String fileId) => 'video_position_$fileId';
}

设计要点:

  • 动态键名使用方法生成,避免拼写错误
  • 所有键名集中管理,便于查找和重命名

3.2 Phase 2: StorageService

class StorageService {
  static final StorageService _instance = StorageService._internal();
  factory StorageService() => _instance;
  StorageService._internal();
 
  SharedPreferences? _prefs;
 
  Future<void> init() async {
    _prefs ??= await SharedPreferences.getInstance();
  }
 
  // 类型安全的访问方法
  Future<ThemeMode> getThemeMode() async {
    final value = _prefs?.getString(StorageKeys.themeMode);
    switch (value) {
      case 'light': return ThemeMode.light;
      case 'dark': return ThemeMode.dark;
      default: return ThemeMode.system;
    }
  }
}

设计要点:

  • 单例模式,懒加载初始化
  • 类型安全的 getter/setter
  • 内部处理空值和默认值

3.3 Phase 3: ThumbnailMemoryCache (LRU)

class ThumbnailMemoryCache {
  static final ThumbnailMemoryCache _instance = ThumbnailMemoryCache._internal();
  factory ThumbnailMemoryCache() => _instance;
  ThumbnailMemoryCache._internal();
 
  final Map<String, Uint8List> _cache = {};
  final Set<String> _loading = {};
 
  int get maxSize => AppConstants.thumbnailMemoryCacheMaxSize;
 
  /// 获取缓存(LRU:访问时移到最近位置)
  Uint8List? get(String fileId) {
    final data = _cache[fileId];
    if (data != null) {
      // 移到最近使用位置(先删除再添加)
      _cache.remove(fileId);
      _cache[fileId] = data;
    }
    return data;
  }
 
  /// 添加到缓存(LRU:超过容量时移除最老的)
  void put(String fileId, Uint8List data) {
    _cache.remove(fileId);  // 如果已存在,先移除
 
    // 检查容量,移除最久未使用的项
    while (_cache.length >= maxSize) {
      _cache.remove(_cache.keys.first);
    }
 
    _cache[fileId] = data;
    _loading.remove(fileId);
  }
}

LRU 算法实现:

  • 利用 Dart MapLinkedHashMap 特性(默认实现)
  • 访问时删除再添加,保持顺序
  • 超出容量时,keys.first 是最老的项

3.4 Phase 4: AppLogger

enum LogLevel { debug, info, warning, error }
 
class AppLogger {
  AppLogger._();
 
  static bool _enabled = kDebugMode;  // 仅 debug 模式启用
  static LogLevel _minLevel = LogLevel.debug;
 
  static void d(String tag, String message) {
    _log(LogLevel.debug, tag, message);
  }
 
  static void e(String tag, String message, [Object? error, StackTrace? stackTrace]) {
    _log(LogLevel.error, tag, message);
    if (error != null) debugPrint('  Error: $error');
    if (stackTrace != null) debugPrint('  Stack: $stackTrace');
  }
 
  static void _log(LogLevel level, String tag, String message) {
    if (!_enabled || level.index < _minLevel.index) return;
 
    final prefix = _levelPrefix(level);
    final timestamp = DateTime.now().toIso8601String().substring(11, 23);
    debugPrint('$prefix [$timestamp] [$tag] $message');
  }
}

设计要点:

  • 级别过滤,可动态调整
  • 时间戳便于追踪
  • tag 分类便于筛选

4. 修改过程

4.1 文件创建顺序

  1. lib/core/constants/app_constants.dart (92 行)
  2. lib/core/constants/storage_keys.dart (101 行)
  3. lib/core/services/storage_service.dart (359 行)
  4. lib/core/utils/thumbnail_memory_cache.dart (95 行)
  5. lib/core/utils/app_logger.dart (77 行)
  6. lib/core/utils/simple_lru_cache.dart (59 行)

4.2 代码替换

4.2.1 debugPrint 替换

涉及文件:

  • lib/core/utils/file_utils.dart
  • lib/core/utils/thumbnail_cache.dart
  • lib/core/services/video_thumbnail_service.dart
  • lib/core/state/app_state.dart

替换模式:

// Before
debugPrint('[VideoThumbnail] Error: $e');
 
// After
AppLogger.e('VideoThumbnail', '[VideoThumbnail] Error: $e');

4.2.2 缓存替换

chat_page.dart:

// Before
final Map<String, Uint8List?> _thumbnailCache = {};
final cached = _thumbnailCache[fileId];
_thumbnailCache[fileId] = thumbData;
 
// After
final ThumbnailMemoryCache _thumbnailCache = ThumbnailMemoryCache();
final cached = _thumbnailCache.get(fileId);
_thumbnailCache.put(fileId, thumbData);

image_preview_page.dart:

// Before (手动 LRU)
static const int _maxCacheSize = 10;
final Map<String, Uint8List> _imageCache = {};
final List<String> _cacheAccessOrder = [];
 
void _addToCache(String id, Uint8List data) {
  _cacheAccessOrder.remove(id);
  _cacheAccessOrder.add(id);
  _imageCache[id] = data;
  while (_cacheAccessOrder.length > _maxCacheSize) {
    final oldestId = _cacheAccessOrder.removeAt(0);
    _imageCache.remove(oldestId);
  }
}
 
// After (使用统一 LRU)
final ThumbnailMemoryCache _imageCache = ThumbnailMemoryCache();
 
void _addToCache(String id, Uint8List data) {
  _imageCache.put(id, data);
}

4.3 冗余 import 清理

清理的 import:

  • lib/core/services/file_operation_service.dart - 删除 file_metadata.dart
  • lib/main.dart - 删除 api_client.dart
  • lib/ui/chat_page.dart - 删除 file_metadata.dart
  • lib/ui/file_search_page.dart - 删除 api_client.dart
  • lib/ui/files_page.dart - 删除 api_client.dart
  • lib/ui/image_preview_page.dart - 删除 file_metadata.dart
  • lib/ui/pdf_preview_page.dart - 删除 file_metadata.dart
  • lib/ui/text_editor_page.dart - 删除 file_metadata.dart
  • lib/ui/unsupported_preview_page.dart - 删除 file_metadata.dart
  • lib/ui/video_player_page.dart - 删除 file_metadata.dart

原因: 这些类型已通过 app_state.dartexport 导出

4.4 dangling_library_doc_comments 修复

涉及文件:

  • lib/core/api/api_client.dart - 删除 library api_client;
  • lib/core/models/send_progress.dart - /// 改为 //
  • lib/core/models/transfer_types.dart - /// 改为 //
  • lib/core/state/transfer_jobs.dart - /// 改为 //
  • lib/ui/widgets/chat_exports.dart - /// 改为 //

原因: 文件顶部的 /// 文档注释没有对应的 library 声明,导致 Flutter 分析器警告


5. 遇到的问题与解决

5.1 ThumbnailCacheAdapter 实现困难

问题: 尝试创建实现 Map<String, Uint8List?> 接口的适配器,需要实现所有 Map 方法。

复杂度:

  • operator []
  • operator []=
  • containsKey
  • forEach
  • map
  • update
  • updateAll
  • entries
  • 等 20+ 个方法

解决方案: 放弃通用适配器,直接修改使用缓存的组件。

5.2 SendMessage import 误删

问题: 在清理 image_preview_page.dart 的 import 时,误删了 send_message.dart,导致 SendMessage 类型未定义。

症状:

Undefined class 'SendMessage'.

解决: 重新添加必要的 import。

教训: 清理 import 时要先检查文件是否使用了该模块中的其他类型。

5.3 PlatformException 类型丢失

问题: 清理 video_thumbnail_service.dart 中的 flutter/foundation.dart import 时,误删了 flutter/services.dart

症状:

The name 'PlatformException' isn't a type and can't be used in an on-catch clause.
The name 'MissingPluginException' isn't a type and can't be used in an on-catch clause.

原因: PlatformExceptionMissingPluginException 来自 flutter/services.dart

解决: 重新添加 flutter/services.dart import。

教训: flutter analyze 的 info 级别提示不一定要修复,有些 import 看似冗余但实际有用。


6. 最终成果

6.1 新增文件

文件行数用途
app_constants.dart92应用常量集中管理
storage_keys.dart101SharedPreferences 键名
storage_service.dart359统一偏好存储服务
thumbnail_memory_cache.dart98LRU 缩略图内存缓存
app_logger.dart77统一日志工具
simple_lru_cache.dart59简单 LRU 缓存实现

6.2 修改的文件

文件修改类型
api_client.dart删除 library 声明
send_progress.dart修复文档注释
transfer_types.dart修复文档注释
transfer_jobs.dart修复文档注释
file_utils.dartAppLogger 替换
thumbnail_cache.dartAppLogger 替换
video_thumbnail_service.dartAppLogger 替换
app_state.dartAppLogger 替换
chat_page.dartThumbnailMemoryCache 替换
image_preview_page.dartThumbnailMemoryCache 替换
main.dart清理冗余 import
file_search_page.dart清理冗余 import
files_page.dart清理冗余 import
pdf_preview_page.dart清理冗余 import
text_editor_page.dart清理冗余 import
unsupported_preview_page.dart清理冗余 import
video_player_page.dart清理冗余 import
file_tiles.dart清理冗余 import
chat_exports.dart修复文档注释

6.3 验证结果

$ flutter analyze
Analyzing client...
 
   info - The import of 'package:flutter/foundation.dart' is unnecessary...
   info - The import of '../../core/models/file_metadata.dart' is unnecessary...
   info - The import of '../../core/api/api_client.dart' is unnecessary...
 
3 issues found. (ran in 4.2s)

结果: 只有 info 级别提示,无错误和警告。


7. 后续工作

7.1 可选优化

  • 将现有代码中的 SharedPreferences 调用迁移到 StorageService
  • files_page.dart 的缩略图缓存也改为 ThumbnailMemoryCache
  • 完成剩余 3 处 info 级别 import 的清理

7.2 延后任务

  • AppState 拆分为多个 Provider(需要独立规划)
  • 使用 Selector 替代 Consumer 精确订阅(需要评估收益)

8. 经验总结

  1. 分阶段优化: 先创建基础设施,再逐步应用,降低风险
  2. 保持功能不变: 重构过程中持续运行 flutter analyze 验证
  3. 谨慎清理 import: 有些 import 看似冗余,但实际提供了必要的类型
  4. 单例模式适用场景: 静态资源缓存适合使用单例
  5. LRU 实现: Dart Map 默认是 LinkedHashMap,天然支持插入顺序

文档版本: 1.0
最后更新: 2026-01-01