PERFORMANCE_OPTIMIZATION_REPORT.md 18 KB

Jigsort Solitaire 性能优化报告

当前性能指标

  • User-perceived crash rate: 2.96% ⚠️
  • User-perceived ANR rate: 1.52% ⚠️
  • User-perceived LMK rate: 0.84% ⚠️
  • Slow cold start rate: 34.33% 🔴

🔴 严重问题 (Critical Issues)

1. 主线程阻塞 - 导致ANR的主要原因

问题1.1: main() 函数中的同步初始化

位置: lib/main.dart:40-140

问题:

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  
  // ❌ 阻塞主线程
  await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
  await Persistence().initialize();  // SharedPreferences 初始化
  RemoteConfig().initialize();       // 远程配置初始化
  
  Directory baseDir = await getApplicationDocumentsDirectory();
  
  if (Persistence().firstRun) {
    final json = await loadJSONFromAsset('assets/builtin/${cfg.Config.firstId}.json');
    await saveJson('work/${cfg.Config.firstId}.json', json);
  }
  
  runApp(MyApp(baseDir: baseDir));
}

影响:

  • 启动时间增加 500-1500ms
  • 低端设备可能触发 ANR (>5秒)
  • 直接导致 Slow cold start rate 34.33%

优化方案:

void main() {
  WidgetsFlutterBinding.ensureInitialized();
  
  // ✅ 立即启动 UI,后台初始化
  runApp(const MyApp());
}

class MyApp extends StatefulWidget {
  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  bool _isInitialized = false;
  
  @override
  void initState() {
    super.initState();
    _initializeAsync();
  }
  
  Future<void> _initializeAsync() async {
    // 并行初始化
    await Future.wait([
      _initFirebase(),
      Persistence().initialize(),
      _prepareFirstRunData(),
    ]);
    
    setState(() => _isInitialized = true);
  }
  
  Future<void> _initFirebase() async {
    if (!kIsWeb && Platform.isAndroid) {
      await Firebase.initializeApp(
        options: DefaultFirebaseOptions.currentPlatform
      );
      // ... 错误处理配置
    }
  }
  
  @override
  Widget build(BuildContext context) {
    if (!_isInitialized) {
      return MaterialApp(
        home: Scaffold(
          body: Center(child: CircularProgressIndicator()),
        ),
      );
    }
    
    return MaterialApp(/* 正常UI */);
  }
}

预期收益:

  • 启动时间减少 40-60%
  • ANR率降低至 <0.5%
  • Slow cold start rate 降至 <15%

问题1.2: BoardPlay._init() 中的图片解码阻塞

位置: lib/play/board_play.dart:700-800

问题:

_init() async {
  // ❌ 大图片解码在主线程,阻塞 UI
  ui.Image image = await itemLoader.getImageBySize(
    bestImageSize.width.round(), 
    bestImageSize.height.round()
  );
  
  // ❌ 卡牌图片解码也在主线程
  final ByteData cardData = await rootBundle.load('assets/images/backcard_red.png');
  final ui.Codec cardCodec = await ui.instantiateImageCodec(
    cardData.buffer.asUint8List(),
    targetWidth: bestCardImageSize.width.round(),
    targetHeight: bestCardImageSize.height.round(),
  );
  final ui.FrameInfo cardFrameInfo = await cardCodec.getNextFrame();
}

影响:

  • 进入游戏页面时卡顿 500-2000ms
  • 高分辨率图片 (2000x3000) 解码可能触发 ANR
  • 导致 ANR rate 1.52%

优化方案:

// 使用 compute 在 isolate 中解码
Future<ui.Image> _decodeImageInIsolate(Uint8List bytes, int width, int height) async {
  return await compute(_decodeImage, {
    'bytes': bytes,
    'width': width,
    'height': height,
  });
}

static Future<ui.Image> _decodeImage(Map<String, dynamic> params) async {
  final bytes = params['bytes'] as Uint8List;
  final width = params['width'] as int;
  final height = params['height'] as int;
  
  final codec = await ui.instantiateImageCodec(
    bytes,
    targetWidth: width,
    targetHeight: height,
  );
  return (await codec.getNextFrame()).image;
}

_init() async {
  // ✅ 并行解码,不阻塞主线程
  final results = await Future.wait([
    _decodeImageInIsolate(imageBytes, width, height),
    _decodeImageInIsolate(cardBytes, cardWidth, cardHeight),
  ]);
  
  final image = results[0];
  final cardImage = results[1];
}

预期收益:

  • 进入游戏页面流畅度提升 70%
  • ANR率降低至 <0.3%

2. 内存泄漏 - 导致崩溃和LMK的主要原因

问题2.1: ui.Image 未及时释放

位置: lib/play/board.dart, lib/models/download.dart

问题:

class Board {
  final ui.Image image;  // ❌ 大图片常驻内存
  ui.Picture? backgroundPicture;  // ❌ Picture 也占用大量内存
  ui.Picture? cardPicture;
  
  dispose() {
    backgroundPicture?.dispose();
    cardPicture?.dispose();
    image.dispose();  // ⚠️ 但调用时机可能太晚
  }
}

class DownloadItem {
  Uint8List? _data;  // ❌ 原始图片数据常驻内存
}

影响:

  • 每个关卡占用 15-30MB 内存
  • 多次进入游戏后内存累积 100MB+
  • 导致 LMK rate 0.84%Crash rate 2.96%

优化方案:

// 1. 立即释放已完成关卡的资源
class _BoardPlayState {
  @override
  void dispose() {
    // ✅ 立即释放
    board?.dispose();
    board = null;
    
    // ✅ 清理下载缓存
    itemLoader.dispose();
    
    super.dispose();
  }
}

// 2. 优化 DownloadItem 内存管理
class DownloadItem {
  Uint8List? _data;
  
  Future<Uint8List> ensureDataLoaded() async {
    if (_data != null) return _data!;
    
    // ✅ 从磁盘读取后立即使用,不长期持有
    final file = await localFile(cachePath);
    final bytes = await file.readAsBytes();
    
    // ⚠️ 不赋值给 _data,避免内存累积
    return bytes;
  }
  
  dispose() {
    _data = null;  // ✅ 立即释放
    subscription?.cancel();
    client?.close();
  }
}

// 3. 关卡完成后立即清理
void _onSuccess() {
  data.workDone(widget.item);
  
  // ✅ 立即释放当前关卡资源
  Future.delayed(Duration(seconds: 2), () {
    board?.dispose();
    board = null;
  });
}

预期收益:

  • 内存占用减少 50-70%
  • LMK率降低至 <0.2%
  • Crash率降低至 <1%

问题2.2: AnimationController 未正确释放

位置: lib/play/board_play.dart:150-250

问题:

class _BoardPlayState {
  late AnimationController _moveAnimationController;
  late AnimationController _mergeAnimationController;
  late AnimationController _prepareAnimationController;
  late AnimationController dealingAnimationController;
  late AnimationController flipAnimationController;
  late AnimationController _successAnimationController;
  late AnimationController _hardModeBannerController;
  
  // ❌ 7个动画控制器,如果页面快速退出可能未完全释放
}

优化方案:

@override
void dispose() {
  // ✅ 先停止所有动画
  _moveAnimationController.stop();
  _mergeAnimationController.stop();
  _prepareAnimationController.stop();
  dealingAnimationController.stop();
  flipAnimationController.stop();
  _successAnimationController.stop();
  _hardModeBannerController.stop();
  
  // ✅ 移除监听器
  _moveAnimationController.removeListener(_moveAnimationListener);
  _moveAnimationController.removeStatusListener(_moveAnimationStatusListener);
  
  // ✅ 释放资源
  _moveAnimationController.dispose();
  _mergeAnimationController.dispose();
  _prepareAnimationController.dispose();
  dealingAnimationController.dispose();
  flipAnimationController.dispose();
  _successAnimationController.dispose();
  _hardModeBannerController.dispose();
  
  // ✅ 清空引用
  moveItems = null;
  _mergeGroups = null;
  
  super.dispose();
}

3. 绘制性能问题 - 导致卡顿和ANR

问题3.1: BoardPainter 过度重绘

位置: lib/play/board_painter.dart:30-100

问题:

class BoardPainter extends CustomPainter {
  BoardPainter({required this.board, required this.prepareAnimation}) 
    : super(repaint: Listenable.merge([board.boardNotifier, prepareAnimation]));
  
  @override
  void paint(Canvas canvas, Size size) {
    // ❌ 每次 boardNotifier 变化都完全重绘
    for (final piece in board.pieces) {
      _drawPiece(canvas, size, piece);  // 可能绘制 25 个碎片
    }
  }
  
  @override
  bool shouldRepaint(covariant BoardPainter oldDelegate) {
    // ❌ 判断条件过于宽松
    return oldDelegate.board.status != board.status;
  }
}

影响:

  • 每次拖动触发 60fps 重绘,每帧绘制 25 个碎片
  • 5x5 宫格时可能掉帧至 30fps
  • 导致 ANR 和用户体验差

优化方案:

class BoardPainter extends CustomPainter {
  // ✅ 使用 RepaintBoundary 隔离静态内容
  @override
  void paint(Canvas canvas, Size size) {
    // 1. 静态背景只绘制一次 (已优化,使用 Picture)
    if (board.backgroundPicture != null) {
      canvas.drawPicture(board.backgroundPicture!);
    }
    
    // 2. 只绘制变化的碎片
    if (_draggingPiece != null) {
      // ✅ 只重绘拖动的碎片和群组
      _drawDraggingPieces(canvas, size);
    } else {
      // ✅ 正常绘制
      for (final piece in board.pieces) {
        _drawPiece(canvas, size, piece);
      }
    }
  }
  
  @override
  bool shouldRepaint(covariant BoardPainter oldDelegate) {
    // ✅ 更精确的判断
    return oldDelegate.board != board || 
           oldDelegate.board.boardNotifier.value != board.boardNotifier.value;
  }
}

// ✅ 在 Widget 层使用 RepaintBoundary
Widget _buildPuzzleCanvas(double width, double height) {
  return RepaintBoundary(  // ✅ 已有,保持
    child: CustomPaint(
      painter: BoardPainter(board: board!, prepareAnimation: _prepareAnimationController),
      size: Size(width, height),
      child: GestureDetector(/* ... */),
    ),
  );
}

预期收益:

  • 拖动流畅度提升至稳定 60fps
  • CPU 占用降低 30-40%

问题3.2: 路径生成未缓存

位置: lib/play/piece.dart:800-1200

问题:

class Piece {
  Path? path;
  Path? innerLinePath;
  Path? outLinePath;
  
  List<Path> generatePaths({bool forceRecalculate = false}) {
    // ❌ 每次绘制都可能重新生成路径
    if (group == null && !forceRecalculate && path != null) {
      return [path!, outLinePath!, innerLinePath!];
    }
    
    // ⚠️ 复杂的路径生成逻辑
    path = _generateClipPath(width, height, borders, board.cornerRadius);
    outLinePath = _generateBorderPath(/* ... */);
    innerLinePath = _generateBorderPath(/* ... */);
  }
}

优化方案:

class Piece {
  Path? _cachedPath;
  Path? _cachedOutLinePath;
  Path? _cachedInnerLinePath;
  int? _cachedBordersHash;  // ✅ 缓存边界状态
  
  List<Path> generatePaths({bool forceRecalculate = false}) {
    final bordersHash = _computeBordersHash();
    
    // ✅ 只在边界状态变化时重新生成
    if (!forceRecalculate && 
        _cachedPath != null && 
        _cachedBordersHash == bordersHash) {
      return [_cachedPath!, _cachedOutLinePath!, _cachedInnerLinePath!];
    }
    
    _cachedPath = _generateClipPath(/* ... */);
    _cachedOutLinePath = _generateBorderPath(/* ... */);
    _cachedInnerLinePath = _generateBorderPath(/* ... */);
    _cachedBordersHash = bordersHash;
    
    return [_cachedPath!, _cachedOutLinePath!, _cachedInnerLinePath!];
  }
  
  int _computeBordersHash() {
    return (_hasTopBorder ? 1 : 0) |
           (_hasRightBorder ? 2 : 0) |
           (_hasBottomBorder ? 4 : 0) |
           (_hasLeftBorder ? 8 : 0);
  }
}

🟡 中等问题 (Medium Issues)

4. 网络请求优化

问题4.1: 图片下载未限流

位置: lib/models/download.dart:15-50

问题:

const maxCachedItems = 1;  // ❌ 只缓存1个,导致频繁下载

class Download {
  DownloadItem download(String url, String cachePath) {
    if (_cache[url] != null) {
      return _cache[url]!;
    } else {
      final item = DownloadItem(url, cachePath);
      _cache[url] = item;
      return item;
    }
  }
}

优化方案:

const maxCachedItems = 3;  // ✅ 增加缓存数量
const maxConcurrentDownloads = 2;  // ✅ 限制并发下载

class Download {
  int _activeDownloads = 0;
  final Queue<Completer<void>> _downloadQueue = Queue();
  
  Future<DownloadItem> download(String url, String cachePath) async {
    // ✅ 限流控制
    while (_activeDownloads >= maxConcurrentDownloads) {
      final completer = Completer<void>();
      _downloadQueue.add(completer);
      await completer.future;
    }
    
    _activeDownloads++;
    try {
      final item = DownloadItem(url, cachePath);
      await item.loadCompleter.future;
      return item;
    } finally {
      _activeDownloads--;
      if (_downloadQueue.isNotEmpty) {
        _downloadQueue.removeFirst().complete();
      }
    }
  }
}

问题4.2: CachedRequest 重复请求

位置: lib/models/cached_request.dart:50-100

问题:

class CachedRequest {
  _remoteLoad() async {
    // ❌ 每次都发起新请求,没有防抖
    final response = await http.get(Uri.parse(url));
  }
  
  @override
  void didChangeAppLifecycleState(AppLifecycleState state) {
    if (state == AppLifecycleState.resumed) {
      // ❌ 每次恢复都刷新,可能过于频繁
      refresh();
    }
  }
}

优化方案:

class CachedRequest {
  DateTime? _lastFetchTime;
  static const _minRefreshInterval = Duration(minutes: 5);
  
  Future<void> refresh() async {
    // ✅ 防抖:5分钟内不重复请求
    if (_lastFetchTime != null &&
        DateTime.now().difference(_lastFetchTime!) < _minRefreshInterval) {
      _log.info('Skipping refresh, too soon since last fetch');
      return;
    }
    
    await _remoteLoad();
  }
  
  @override
  void didChangeAppLifecycleState(AppLifecycleState state) {
    if (state == AppLifecycleState.resumed) {
      // ✅ 智能刷新:只在数据过期时刷新
      final timeSinceLastFetch = _lastFetchTime != null
          ? DateTime.now().difference(_lastFetchTime!)
          : Duration(days: 1);
      
      if (timeSinceLastFetch > Duration(hours: 1)) {
        refresh();
      }
    }
  }
}

5. 数据持久化优化

问题5.1: SharedPreferences 频繁写入

位置: lib/persistence/persistence.dart:200-250

问题:

class PreferencesValue<T> {
  set value(T v) {
    _value = v;
    // ❌ 每次赋值都立即写入磁盘
    if (_value is bool) {
      unawaited(prefs.setBool(key, _value as bool));
    }
    // ...
  }
}

优化方案:

class PreferencesValue<T> {
  Timer? _saveTimer;
  
  set value(T v) {
    _value = v;
    
    // ✅ 延迟批量写入
    _saveTimer?.cancel();
    _saveTimer = Timer(Duration(seconds: 1), () {
      _saveToPrefs(v);
    });
  }
  
  void _saveToPrefs(T v) {
    if (v is bool) {
      unawaited(prefs.setBool(key, v));
    }
    // ...
  }
  
  void dispose() {
    _saveTimer?.cancel();
    _saveToPrefs(_value);  // 立即保存
  }
}

🟢 低优先级优化 (Low Priority)

6. 代码质量改进

6.1 移除未使用的代码

// lib/main.dart:180-200
// ❌ 注释掉的旧代码应删除
// ProxyProvider2<SettingsController, ValueNotifier<AppLifecycleState>, AudioController>(
//   lazy: false,
//   create: (context) => AudioController()..initialize(),
//   ...
// ),

6.2 优化日志输出

// ❌ 生产环境仍然输出大量日志
_log.info('_onPanStart');
_log.info('_onPanUpdate');

// ✅ 使用条件编译
if (kDebugMode) {
  _log.info('_onPanStart');
}

📊 优化优先级和预期收益

优先级 问题 预期收益 实施难度 预计工时
🔴 P0 主线程阻塞 (main初始化) ANR -60%, 启动时间 -50% 4h
🔴 P0 图片解码阻塞 ANR -40%, 卡顿 -70% 6h
🔴 P0 内存泄漏 (Image未释放) Crash -50%, LMK -70% 3h
🟡 P1 AnimationController泄漏 Crash -20% 2h
🟡 P1 绘制性能优化 流畅度 +30% 4h
🟡 P2 网络请求限流 网络稳定性 +20% 2h
🟢 P3 SharedPreferences优化 磁盘IO -50% 2h

总计预计工时: 23小时

预期最终指标:

  • Crash rate: 2.96% → <1.0%
  • ANR rate: 1.52% → <0.3%
  • LMK rate: 0.84% → <0.2%
  • Slow cold start: 34.33% → <12%

🛠️ 实施建议

第一阶段 (Week 1) - 解决启动和ANR问题

  1. 优化 main() 函数异步初始化
  2. 图片解码移至 isolate
  3. 立即释放已完成关卡的 Image 资源

第二阶段 (Week 2) - 解决内存和崩溃问题

  1. 修复 AnimationController 泄漏
  2. 优化 DownloadItem 内存管理
  3. 添加内存监控和自动清理

第三阶段 (Week 3) - 性能打磨

  1. 优化绘制性能
  2. 网络请求限流
  3. 数据持久化优化

📝 监控建议

添加性能监控埋点

// 启动时间监控
class PerformanceMonitor {
  static final Stopwatch _startupTimer = Stopwatch();
  
  static void startMonitoring() {
    _startupTimer.start();
  }
  
  static void reportStartupComplete() {
    _startupTimer.stop();
    FirebaseHelper.logEvent('app_startup_time', {
      'duration_ms': _startupTimer.elapsedMilliseconds,
    });
  }
}

// 内存监控
class MemoryMonitor {
  static void checkMemoryUsage() {
    final info = ProcessInfo.currentRss;
    if (info > 200 * 1024 * 1024) {  // 200MB
      FirebaseHelper.logEvent('high_memory_usage', {
        'memory_mb': info / (1024 * 1024),
      });
    }
  }
}

✅ 验证清单

优化完成后,请验证以下指标:

  • 冷启动时间 < 2秒 (中端设备)
  • 进入游戏页面无明显卡顿 (< 500ms)
  • 拖动碎片流畅 (稳定 60fps)
  • 连续玩10关后内存占用 < 150MB
  • 快速进出游戏页面无崩溃
  • 低端设备 (2GB RAM) 可正常运行

报告生成时间: 2024 分析工具: 静态代码分析 + Google Play Console 数据