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

**当前问题**:
```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<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 数量

**当前问题**:
```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
