# BoardPlay 内存占用分析与优化方案 ## 🔴 当前问题 **实测内存**: 400-600MB (Release 模式) **目标内存**: <150MB (Release 模式) **差距**: 需要减少 60-75% 的内存占用 --- ## 📊 内存占用来源分析 ### 1. **ui.Image 对象** - 最大占用 (约 200-300MB) ```dart // 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) ```dart // 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) **当前问题**: ```dart // device.dart String get suggestedQuality { if (isTablet) return "2400"; // 太高! if (isLowEndDevice) return "1200"; return "1800"; // 默认太高! } ``` **优化方案**: ```dart 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%) **优化方案**: ```dart // 在 image_decoder.dart 中 Future _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 _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) **当前问题**: ```dart // board.dart ui.Picture? backgroundPicture; // 一直持有 ui.Picture? cardPicture; // 一直持有 ``` **优化方案**: ```dart 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 缓存 **当前问题**: ```dart // piece.dart class Piece { Path? path; Path? innerLinePath; Path? outLinePath; // 25个碎片 × 3个Path × 0.5MB = 37.5MB } ``` **优化方案**: ```dart class Piece { Path? _cachedPath; Path? _cachedInnerLinePath; Path? _cachedOutLinePath; // ✅ 只在需要时生成,用完立即释放 List 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 数量 **当前问题**: ```dart // 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个控制器 ``` **优化方案**: ```dart // 复用控制器 late AnimationController _primaryController; // 主要动画 late AnimationController _secondaryController; // 次要动画 // 根据需要切换用途 void _startMoveAnimation() { _primaryController.duration = Duration(milliseconds: 200); _primaryController.forward(from: 0.0); } ``` --- ### 优先级 P2: 进一步优化 #### 6. 使用纹理压缩 ```dart // 使用 ETC2 或 ASTC 压缩格式 // 需要在服务端预处理图片 ``` #### 7. 分块加载大图 ```dart // 只加载可见区域 // 适用于超大图片 ``` --- ## 🔧 立即可应用的优化 ### 修改 1: device.dart ```dart String get suggestedQuality { if (isTablet) return "1600"; // 2400 → 1600 if (isLowEndDevice) return "1000"; // 1200 → 1000 return "1200"; // 1800 → 1200 } ``` ### 修改 2: board.dart ```dart void start() { _status = BoardStatus.playing; // ✅ 立即释放 Picture backgroundPicture?.dispose(); backgroundPicture = null; cardPicture?.dispose(); cardPicture = null; invalidate(); } ``` ### 修改 3: board_play.dart ```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%** | --- ## ✅ 验证方法 ```dart // 在 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