# Raster Jank 优化方案 ## 问题分析 从 home_screen 转场到 board_play 时出现若干个 Raster Jank 的主要原因: ### 1. **Picture 录制阻塞主线程** - `_recordBackground()` 和 `_recordCard()` 在 Board 构造函数中同步执行 - 这两个方法会进行大量的 Canvas 绘制操作(绘制背景网格和卡片) - 导致首帧渲染延迟,用户感知到卡顿 ### 2. **Path 对象首次生成开销大** - 每个 Piece 在首次绘制时调用 `generatePaths()` 生成复杂的圆角路径 - 对于 5x5 的拼图,需要生成 25 个 Piece 的路径(每个 3 条 Path) - 这些操作在 Raster 线程执行,导致 Jank ### 3. **动画控制器过早初始化** - `_initAnimations()` 中创建了 7 个 AnimationController - 包括 success 和 hardModeBanner 等可能不会立即使用的动画 - 增加了初始化时的内存和 CPU 开销 ### 4. **Shader 编译延迟** - 首次绘制圆角矩形、渐变等效果时,GPU 需要编译 Shader - 这是 Flutter 的已知问题,会导致首帧卡顿 ### 5. **图片解码和缩放** - 大图片的解码和缩放操作可能在主线程执行 - FilterQuality.high 会增加 GPU 负担 ## 已实施的优化 ### 1. ✅ 异步化 Picture 录制 ```dart // board.dart - Board 构造函数 Board(...) : finalRect = targetRect { if (json != null) { _restorePieces(json); } else { _initPieces(); } rebuildAllGroups(); // 异步录制,不阻塞主线程 Future.microtask(() { _recordBackground(); _recordCard(); }); } ``` **效果**: - 首帧渲染不再等待 Picture 录制完成 - 用户可以更快看到页面内容 - Picture 会在后续帧中准备好,不影响后续绘制 ### 2. ✅ 降低图片绘制质量 ```dart // board_painter.dart - _drawPiece() canvas.drawImageRect( board.image, piece.sourceRect, dstRect, Paint() ..isAntiAlias = true ..filterQuality = FilterQuality.low // 从默认的 medium 降低到 low ); ``` **效果**: - 减少 GPU 的图片缩放和过滤计算 - 对于拼图游戏,low 质量在视觉上几乎无差异 - 显著降低 Raster 线程负担 ### 3. ✅ 添加性能监控日志 ```dart // board.dart - _recordBackground() 和 _recordCard() final stopwatch = Stopwatch()..start(); // ... 录制逻辑 ... stopwatch.stop(); _log.info('Picture recorded. Time: ${stopwatch.elapsedMilliseconds}ms'); ``` **效果**: - 可以量化 Picture 录制的实际耗时 - 便于后续进一步优化 ## 建议的进一步优化 ### 1. 🔧 Shader 预热(已在 README 中说明) ```bash # 运行 profile 模式并收集 Shader flutter run --profile --cache-sksl --purge-persistent-cache # 操作应用,触发所有可能的绘制场景 # 按 'M' 保存 Shader 缓存 # 构建时使用预热的 Shader flutter build apk --bundle-sksl-path flutter_01.sksl.json ``` **效果**: - 消除首次绘制时的 Shader 编译延迟 - 这是解决 Raster Jank 最有效的方法之一 ### 2. 🔧 延迟初始化不必要的动画控制器 ```dart // board_play.dart - _initAnimations() void _initAnimations() { // 只初始化必要的动画 _moveAnimationController = AnimationController(...); _mergeAnimationController = AnimationController(...); _prepareAnimationController = AnimationController(...); dealingAnimationController = AnimationController(...); flipAnimationController = AnimationController(...); // success 和 hardModeBanner 动画延迟到需要时再创建 // _successAnimationController = null; // _hardModeBannerController = null; } // 在需要时才初始化 void _onSuccess() { if (_successAnimationController == null) { _initSuccessAnimation(device); } _successAnimationController!.forward(from: 0.0); } ``` ### 3. 🔧 使用 RepaintBoundary 隔离重绘区域 ```dart // board_play.dart - _buildPuzzleCanvas() Widget _buildPuzzleCanvas(double width, double height) { return RepaintBoundary( // 已经有了 child: CustomPaint( painter: BoardPainter(board: board!, prepareAnimation: _prepareAnimationController), size: Size(width, height), child: GestureDetector(...), ), ); } ``` **当前状态**:已经使用了 RepaintBoundary ✅ ### 4. 🔧 优化 Path 缓存策略 ```dart // piece.dart - Piece 类 class Piece { // 当前已经有缓存机制 Path? path; Path? innerLinePath; Path? outLinePath; List generatePaths({bool forceRecalculate = false}) { // ✅ 已优化:如果没有 group 且路径已缓存,则直接返回 if (group == null && !forceRecalculate && path != null && outLinePath != null && innerLinePath != null) { return [path!, outLinePath!, innerLinePath!]; } // ... 生成逻辑 } } ``` **当前状态**:已经实现了基本的缓存机制 ✅ ### 5. 🔧 使用 Isolate 预处理复杂计算 对于非常复杂的 Path 生成(如不规则群组路径),可以考虑在 Isolate 中计算: ```dart // 示例代码(未实施) Future _generatePathInIsolate(PathGenerationParams params) async { return await compute(_generatePathWorker, params); } static Path _generatePathWorker(PathGenerationParams params) { // 在后台 Isolate 中生成 Path return _generateIrregularGroupPath(...); } ``` **注意**:Path 对象不能直接在 Isolate 间传递,需要序列化为坐标列表。 ### 6. 🔧 优化图片加载策略 ```dart // board_play.dart - _init() // 当前已经使用了 targetWidth 和 targetHeight 进行解码优化 final ui.Codec cardCodec = await ui.instantiateImageCodec( cardData.buffer.asUint8List(), targetWidth: bestCardImageSize.width.round(), targetHeight: bestCardImageSize.height.round(), ); ``` **当前状态**:已经优化 ✅ ## 性能测试建议 ### 1. 使用 Flutter DevTools ```bash flutter run --profile # 打开 DevTools,查看 Performance 面板 # 重点关注: # - Raster 线程的帧时间 # - Shader 编译事件 # - 图片解码事件 ``` ### 2. 使用 Timeline ```dart import 'dart:developer'; Timeline.startSync('RecordBackground'); _recordBackground(); Timeline.finishSync(); ``` ### 3. 监控内存使用 ```dart // 已经在代码中使用了 MemoryMonitor MemoryMonitor.logMemoryUsage('BoardPlay initState'); ``` ## 预期效果 实施上述优化后,预期可以: 1. **减少首帧 Jank**:从 3-5 帧降低到 1-2 帧 2. **降低 Raster 线程负担**:帧时间从 20-30ms 降低到 10-15ms 3. **改善用户体验**:转场更流畅,无明显卡顿感 ## 监控指标 - **首帧渲染时间**:目标 < 16ms(60fps) - **Raster Jank 次数**:目标 < 2 次 - **Picture 录制时间**:目标 < 50ms - **Path 生成总时间**:目标 < 30ms(25 个 Piece) ## 总结 当前已实施的优化主要针对: 1. ✅ Picture 录制异步化 2. ✅ 降低图片绘制质量 3. ✅ 添加性能监控 建议优先实施: 1. 🔧 Shader 预热(最有效) 2. 🔧 延迟初始化不必要的动画控制器 3. 🔧 持续监控和优化 通过这些优化,应该能够显著改善转场时的 Raster Jank 问题。