实测内存: 400-600MB (Release 模式) 目标内存: <150MB (Release 模式) 差距: 需要减少 60-75% 的内存占用
// 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
问题:
计算:
主图片: 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 (因为有多份拷贝和临时对象)
// download.dart
class DownloadItem {
Uint8List? _data; // 原始图片数据: 5-15MB
}
// 问题: 可能同时缓存多个图片
const maxCachedItems = 1; // 但实际可能有多个
当前问题:
// 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";
}
预期效果:
优化方案:
// 在 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;
}
预期效果:
当前问题:
// 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');
}
}
}
预期效果:
当前问题:
// 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;
}
}
当前问题:
// 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);
}
// 使用 ETC2 或 ASTC 压缩格式
// 需要在服务端预处理图片
// 只加载可见区域
// 适用于超大图片
String get suggestedQuality {
if (isTablet) return "1600"; // 2400 → 1600
if (isLowEndDevice) return "1000"; // 1200 → 1000
return "1200"; // 1800 → 1200
}
void start() {
_status = BoardStatus.playing;
// ✅ 立即释放 Picture
backgroundPicture?.dispose();
backgroundPicture = null;
cardPicture?.dispose();
cardPicture = null;
invalidate();
}
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 ✅
device.dart 降低图片质量board.dart 释放 Picture_onSuccess() 立即清理预计总时间: 3.5小时 预计效果: 内存从 400-600MB 降至 150-200MB