MEMORY_USAGE_ANALYSIS.md 8.8 KB

BoardPlay 内存占用分析与优化方案

🔴 当前问题

实测内存: 400-600MB (Release 模式) 目标内存: <150MB (Release 模式) 差距: 需要减少 60-75% 的内存占用


📊 内存占用来源分析

1. ui.Image 对象 - 最大占用 (约 200-300MB)

// board_play.dart
ui.Image image;  // 主图片: 1800x2700 RGBA = 19.4MB
ui.Image cardImage;  // 卡牌图片: 多个 = 5-10MB

// board.dart
ui.Picture backgroundPicture;  // 背景 Picture: 20-30MB
ui.Picture cardPicture;  // 卡牌 Picture: 5-10MB

问题:

  • 图片分辨率过高 (1800x2700)
  • RGBA 格式占用 4 bytes/pixel
  • Picture 对象额外占用内存

计算:

主图片: 1800 × 2700 × 4 bytes = 19.4 MB
25个碎片 Path: 25 × 0.5MB = 12.5 MB
Picture 缓存: 20-30 MB
总计: ~50-60 MB (理论值)
实际: 200-300 MB (因为有多份拷贝和临时对象)

2. DownloadItem 数据 - 中等占用 (约 50-100MB)

// download.dart
class DownloadItem {
  Uint8List? _data;  // 原始图片数据: 5-15MB
}

// 问题: 可能同时缓存多个图片
const maxCachedItems = 1;  // 但实际可能有多个

3. Flutter 引擎开销 - 固定占用 (约 50-100MB)

  • Dart VM
  • Skia 渲染引擎
  • 系统库

4. 其他占用 (约 50-100MB)

  • AnimationController (7个)
  • CustomPainter 缓存
  • 广告 SDK
  • 音频资源

🎯 优化方案

优先级 P0: 立即优化 (预计减少 200-300MB)

1. 降低图片分辨率 (减少 100-150MB)

当前问题:

// device.dart
String get suggestedQuality {
  if (isTablet) return "2400";  // 太高!
  if (isLowEndDevice) return "1200";
  return "1800";  // 默认太高!
}

优化方案:

String get suggestedQuality {
  // ✅ 基于实际屏幕分辨率计算
  final screenWidth = screenSize.width;
  final dpr = effectivePixelRatio;
  final targetPixels = screenWidth * dpr * aspectRatio;
  
  if (isTablet) {
    return targetPixels > 2000 ? "2000" : "1600";
  }
  
  if (isLowEndDevice) {
    return "1000";  // 降低到 1000
  }
  
  // 普通设备:根据屏幕计算
  if (targetPixels > 1600) return "1600";
  if (targetPixels > 1200) return "1200";
  return "1000";
}

预期效果:

  • 1800 → 1200: 图片大小减少 56%
  • 内存占用: 19.4MB → 8.6MB (单张)
  • 总内存减少: ~100MB

2. 使用 RGB 格式替代 RGBA (减少 25%)

优化方案:

// 在 image_decoder.dart 中
Future<ui.Image> _decodeImageInIsolate(_DecodeParams params) async {
  final codec = await ui.instantiateImageCodec(
    params.bytes,
    targetWidth: params.targetWidth,
    targetHeight: params.targetHeight,
    allowUpscaling: params.allowUpscaling,
  );

  final frameInfo = await codec.getNextFrame();
  final image = frameInfo.image;
  
  // ✅ 转换为 RGB 格式(如果图片不需要透明度)
  if (!params.needsAlpha) {
    return _convertToRGB(image);
  }
  
  return image;
}

Future<ui.Image> _convertToRGB(ui.Image rgbaImage) async {
  final recorder = ui.PictureRecorder();
  final canvas = Canvas(recorder);
  
  // 绘制到不透明背景
  canvas.drawColor(Colors.white, BlendMode.src);
  canvas.drawImage(rgbaImage, Offset.zero, Paint());
  
  final picture = recorder.endRecording();
  final img = await picture.toImage(
    rgbaImage.width,
    rgbaImage.height,
  );
  
  rgbaImage.dispose();
  picture.dispose();
  
  return img;
}

预期效果:

  • RGBA (4 bytes) → RGB (3 bytes): 减少 25%
  • 8.6MB → 6.5MB

3. 立即释放 Picture 对象 (减少 30-50MB)

当前问题:

// board.dart
ui.Picture? backgroundPicture;  // 一直持有
ui.Picture? cardPicture;  // 一直持有

优化方案:

class Board {
  ui.Picture? backgroundPicture;
  ui.Picture? cardPicture;
  
  // ✅ 游戏开始后立即释放 Picture
  void start() {
    _status = BoardStatus.playing;
    
    // 发牌动画结束,立即释放 Picture
    _releasePictures();
    
    invalidate();
  }
  
  void _releasePictures() {
    if (backgroundPicture != null) {
      backgroundPicture!.dispose();
      backgroundPicture = null;
      _log.info('Background picture released');
    }
    
    if (cardPicture != null) {
      cardPicture!.dispose();
      cardPicture = null;
      _log.info('Card picture released');
    }
  }
}

预期效果:

  • 减少 30-50MB 内存占用

优先级 P1: 重要优化 (预计减少 50-100MB)

4. 优化 Path 缓存

当前问题:

// piece.dart
class Piece {
  Path? path;
  Path? innerLinePath;
  Path? outLinePath;
  // 25个碎片 × 3个Path × 0.5MB = 37.5MB
}

优化方案:

class Piece {
  Path? _cachedPath;
  Path? _cachedInnerLinePath;
  Path? _cachedOutLinePath;
  
  // ✅ 只在需要时生成,用完立即释放
  List<Path> generatePathsOnDemand() {
    final paths = generatePaths();
    
    // 游戏进行中,不缓存 Path
    if (board.status == BoardStatus.playing) {
      return paths;
    }
    
    // 只在动画时缓存
    _cachedPath = paths[0];
    _cachedInnerLinePath = paths[1];
    _cachedOutLinePath = paths[2];
    
    return paths;
  }
  
  void clearPathCache() {
    _cachedPath = null;
    _cachedInnerLinePath = null;
    _cachedOutLinePath = null;
  }
}

5. 限制 AnimationController 数量

当前问题:

// board_play.dart
late AnimationController _moveAnimationController;
late AnimationController _mergeAnimationController;
late AnimationController _prepareAnimationController;
late AnimationController dealingAnimationController;
late AnimationController flipAnimationController;
late AnimationController _successAnimationController;
late AnimationController _hardModeBannerController;
// 7个控制器

优化方案:

// 复用控制器
late AnimationController _primaryController;  // 主要动画
late AnimationController _secondaryController;  // 次要动画

// 根据需要切换用途
void _startMoveAnimation() {
  _primaryController.duration = Duration(milliseconds: 200);
  _primaryController.forward(from: 0.0);
}

优先级 P2: 进一步优化

6. 使用纹理压缩

// 使用 ETC2 或 ASTC 压缩格式
// 需要在服务端预处理图片

7. 分块加载大图

// 只加载可见区域
// 适用于超大图片

🔧 立即可应用的优化

修改 1: device.dart

String get suggestedQuality {
  if (isTablet) return "1600";  // 2400 → 1600
  if (isLowEndDevice) return "1000";  // 1200 → 1000
  return "1200";  // 1800 → 1200
}

修改 2: board.dart

void start() {
  _status = BoardStatus.playing;
  
  // ✅ 立即释放 Picture
  backgroundPicture?.dispose();
  backgroundPicture = null;
  cardPicture?.dispose();
  cardPicture = null;
  
  invalidate();
}

修改 3: board_play.dart

void _onSuccess() {
  // ... 现有代码
  
  // ✅ 立即清理资源
  Timer(Duration(seconds: 1), () {
    if (board != null) {
      board!.dispose();
      board = null;
    }
  });
}

📊 预期优化效果

优化项 当前 优化后 减少
图片分辨率 1800px 1200px -100MB
Picture 缓存 持有 释放 -40MB
RGB 格式 RGBA RGB -30MB
Path 缓存 全部 按需 -20MB
总计 400-600MB 150-200MB -60%

✅ 验证方法

// 在 board_play.dart 中添加
@override
void initState() {
  super.initState();
  MemoryMonitor.logMemoryUsage('BoardPlay init');
}

void _init() async {
  MemoryMonitor.logMemoryUsage('Before image decode');
  
  // 解码图片
  final images = await ImageDecoder.decodeImages(...);
  
  MemoryMonitor.logMemoryUsage('After image decode');
  
  // 创建 Board
  board = await Board.create(...);
  
  MemoryMonitor.logMemoryUsage('After board create');
}

@override
void dispose() {
  MemoryMonitor.logMemoryUsage('Before dispose');
  board?.dispose();
  MemoryMonitor.logMemoryUsage('After dispose');
  super.dispose();
}

预期日志:

优化前:
[BoardPlay init] Memory: 120 MB
[After image decode] Memory: 350 MB  ❌
[After board create] Memory: 450 MB  ❌
[After dispose] Memory: 130 MB

优化后:
[BoardPlay init] Memory: 120 MB
[After image decode] Memory: 180 MB  ✅
[After board create] Memory: 200 MB  ✅
[After dispose] Memory: 125 MB  ✅

🎯 行动计划

第一步 (立即执行,30分钟)

  1. 修改 device.dart 降低图片质量
  2. 修改 board.dart 释放 Picture
  3. 测试验证

第二步 (1小时)

  1. 优化 _onSuccess() 立即清理
  2. 添加内存监控日志
  3. 测试多个关卡

第三步 (2小时)

  1. 实现 RGB 格式转换
  2. 优化 Path 缓存策略
  3. 全面测试

预计总时间: 3.5小时 预计效果: 内存从 400-600MB 降至 150-200MB