main() 函数中的同步初始化位置: lib/main.dart:40-140
问题:
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// ❌ 阻塞主线程
await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
await Persistence().initialize(); // SharedPreferences 初始化
RemoteConfig().initialize(); // 远程配置初始化
Directory baseDir = await getApplicationDocumentsDirectory();
if (Persistence().firstRun) {
final json = await loadJSONFromAsset('assets/builtin/${cfg.Config.firstId}.json');
await saveJson('work/${cfg.Config.firstId}.json', json);
}
runApp(MyApp(baseDir: baseDir));
}
影响:
优化方案:
void main() {
WidgetsFlutterBinding.ensureInitialized();
// ✅ 立即启动 UI,后台初始化
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
bool _isInitialized = false;
@override
void initState() {
super.initState();
_initializeAsync();
}
Future<void> _initializeAsync() async {
// 并行初始化
await Future.wait([
_initFirebase(),
Persistence().initialize(),
_prepareFirstRunData(),
]);
setState(() => _isInitialized = true);
}
Future<void> _initFirebase() async {
if (!kIsWeb && Platform.isAndroid) {
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform
);
// ... 错误处理配置
}
}
@override
Widget build(BuildContext context) {
if (!_isInitialized) {
return MaterialApp(
home: Scaffold(
body: Center(child: CircularProgressIndicator()),
),
);
}
return MaterialApp(/* 正常UI */);
}
}
预期收益:
BoardPlay._init() 中的图片解码阻塞位置: lib/play/board_play.dart:700-800
问题:
_init() async {
// ❌ 大图片解码在主线程,阻塞 UI
ui.Image image = await itemLoader.getImageBySize(
bestImageSize.width.round(),
bestImageSize.height.round()
);
// ❌ 卡牌图片解码也在主线程
final ByteData cardData = await rootBundle.load('assets/images/backcard_red.png');
final ui.Codec cardCodec = await ui.instantiateImageCodec(
cardData.buffer.asUint8List(),
targetWidth: bestCardImageSize.width.round(),
targetHeight: bestCardImageSize.height.round(),
);
final ui.FrameInfo cardFrameInfo = await cardCodec.getNextFrame();
}
影响:
优化方案:
// 使用 compute 在 isolate 中解码
Future<ui.Image> _decodeImageInIsolate(Uint8List bytes, int width, int height) async {
return await compute(_decodeImage, {
'bytes': bytes,
'width': width,
'height': height,
});
}
static Future<ui.Image> _decodeImage(Map<String, dynamic> params) async {
final bytes = params['bytes'] as Uint8List;
final width = params['width'] as int;
final height = params['height'] as int;
final codec = await ui.instantiateImageCodec(
bytes,
targetWidth: width,
targetHeight: height,
);
return (await codec.getNextFrame()).image;
}
_init() async {
// ✅ 并行解码,不阻塞主线程
final results = await Future.wait([
_decodeImageInIsolate(imageBytes, width, height),
_decodeImageInIsolate(cardBytes, cardWidth, cardHeight),
]);
final image = results[0];
final cardImage = results[1];
}
预期收益:
ui.Image 未及时释放位置: lib/play/board.dart, lib/models/download.dart
问题:
class Board {
final ui.Image image; // ❌ 大图片常驻内存
ui.Picture? backgroundPicture; // ❌ Picture 也占用大量内存
ui.Picture? cardPicture;
dispose() {
backgroundPicture?.dispose();
cardPicture?.dispose();
image.dispose(); // ⚠️ 但调用时机可能太晚
}
}
class DownloadItem {
Uint8List? _data; // ❌ 原始图片数据常驻内存
}
影响:
优化方案:
// 1. 立即释放已完成关卡的资源
class _BoardPlayState {
@override
void dispose() {
// ✅ 立即释放
board?.dispose();
board = null;
// ✅ 清理下载缓存
itemLoader.dispose();
super.dispose();
}
}
// 2. 优化 DownloadItem 内存管理
class DownloadItem {
Uint8List? _data;
Future<Uint8List> ensureDataLoaded() async {
if (_data != null) return _data!;
// ✅ 从磁盘读取后立即使用,不长期持有
final file = await localFile(cachePath);
final bytes = await file.readAsBytes();
// ⚠️ 不赋值给 _data,避免内存累积
return bytes;
}
dispose() {
_data = null; // ✅ 立即释放
subscription?.cancel();
client?.close();
}
}
// 3. 关卡完成后立即清理
void _onSuccess() {
data.workDone(widget.item);
// ✅ 立即释放当前关卡资源
Future.delayed(Duration(seconds: 2), () {
board?.dispose();
board = null;
});
}
预期收益:
AnimationController 未正确释放位置: lib/play/board_play.dart:150-250
问题:
class _BoardPlayState {
late AnimationController _moveAnimationController;
late AnimationController _mergeAnimationController;
late AnimationController _prepareAnimationController;
late AnimationController dealingAnimationController;
late AnimationController flipAnimationController;
late AnimationController _successAnimationController;
late AnimationController _hardModeBannerController;
// ❌ 7个动画控制器,如果页面快速退出可能未完全释放
}
优化方案:
@override
void dispose() {
// ✅ 先停止所有动画
_moveAnimationController.stop();
_mergeAnimationController.stop();
_prepareAnimationController.stop();
dealingAnimationController.stop();
flipAnimationController.stop();
_successAnimationController.stop();
_hardModeBannerController.stop();
// ✅ 移除监听器
_moveAnimationController.removeListener(_moveAnimationListener);
_moveAnimationController.removeStatusListener(_moveAnimationStatusListener);
// ✅ 释放资源
_moveAnimationController.dispose();
_mergeAnimationController.dispose();
_prepareAnimationController.dispose();
dealingAnimationController.dispose();
flipAnimationController.dispose();
_successAnimationController.dispose();
_hardModeBannerController.dispose();
// ✅ 清空引用
moveItems = null;
_mergeGroups = null;
super.dispose();
}
BoardPainter 过度重绘位置: lib/play/board_painter.dart:30-100
问题:
class BoardPainter extends CustomPainter {
BoardPainter({required this.board, required this.prepareAnimation})
: super(repaint: Listenable.merge([board.boardNotifier, prepareAnimation]));
@override
void paint(Canvas canvas, Size size) {
// ❌ 每次 boardNotifier 变化都完全重绘
for (final piece in board.pieces) {
_drawPiece(canvas, size, piece); // 可能绘制 25 个碎片
}
}
@override
bool shouldRepaint(covariant BoardPainter oldDelegate) {
// ❌ 判断条件过于宽松
return oldDelegate.board.status != board.status;
}
}
影响:
优化方案:
class BoardPainter extends CustomPainter {
// ✅ 使用 RepaintBoundary 隔离静态内容
@override
void paint(Canvas canvas, Size size) {
// 1. 静态背景只绘制一次 (已优化,使用 Picture)
if (board.backgroundPicture != null) {
canvas.drawPicture(board.backgroundPicture!);
}
// 2. 只绘制变化的碎片
if (_draggingPiece != null) {
// ✅ 只重绘拖动的碎片和群组
_drawDraggingPieces(canvas, size);
} else {
// ✅ 正常绘制
for (final piece in board.pieces) {
_drawPiece(canvas, size, piece);
}
}
}
@override
bool shouldRepaint(covariant BoardPainter oldDelegate) {
// ✅ 更精确的判断
return oldDelegate.board != board ||
oldDelegate.board.boardNotifier.value != board.boardNotifier.value;
}
}
// ✅ 在 Widget 层使用 RepaintBoundary
Widget _buildPuzzleCanvas(double width, double height) {
return RepaintBoundary( // ✅ 已有,保持
child: CustomPaint(
painter: BoardPainter(board: board!, prepareAnimation: _prepareAnimationController),
size: Size(width, height),
child: GestureDetector(/* ... */),
),
);
}
预期收益:
位置: lib/play/piece.dart:800-1200
问题:
class Piece {
Path? path;
Path? innerLinePath;
Path? outLinePath;
List<Path> generatePaths({bool forceRecalculate = false}) {
// ❌ 每次绘制都可能重新生成路径
if (group == null && !forceRecalculate && path != null) {
return [path!, outLinePath!, innerLinePath!];
}
// ⚠️ 复杂的路径生成逻辑
path = _generateClipPath(width, height, borders, board.cornerRadius);
outLinePath = _generateBorderPath(/* ... */);
innerLinePath = _generateBorderPath(/* ... */);
}
}
优化方案:
class Piece {
Path? _cachedPath;
Path? _cachedOutLinePath;
Path? _cachedInnerLinePath;
int? _cachedBordersHash; // ✅ 缓存边界状态
List<Path> generatePaths({bool forceRecalculate = false}) {
final bordersHash = _computeBordersHash();
// ✅ 只在边界状态变化时重新生成
if (!forceRecalculate &&
_cachedPath != null &&
_cachedBordersHash == bordersHash) {
return [_cachedPath!, _cachedOutLinePath!, _cachedInnerLinePath!];
}
_cachedPath = _generateClipPath(/* ... */);
_cachedOutLinePath = _generateBorderPath(/* ... */);
_cachedInnerLinePath = _generateBorderPath(/* ... */);
_cachedBordersHash = bordersHash;
return [_cachedPath!, _cachedOutLinePath!, _cachedInnerLinePath!];
}
int _computeBordersHash() {
return (_hasTopBorder ? 1 : 0) |
(_hasRightBorder ? 2 : 0) |
(_hasBottomBorder ? 4 : 0) |
(_hasLeftBorder ? 8 : 0);
}
}
位置: lib/models/download.dart:15-50
问题:
const maxCachedItems = 1; // ❌ 只缓存1个,导致频繁下载
class Download {
DownloadItem download(String url, String cachePath) {
if (_cache[url] != null) {
return _cache[url]!;
} else {
final item = DownloadItem(url, cachePath);
_cache[url] = item;
return item;
}
}
}
优化方案:
const maxCachedItems = 3; // ✅ 增加缓存数量
const maxConcurrentDownloads = 2; // ✅ 限制并发下载
class Download {
int _activeDownloads = 0;
final Queue<Completer<void>> _downloadQueue = Queue();
Future<DownloadItem> download(String url, String cachePath) async {
// ✅ 限流控制
while (_activeDownloads >= maxConcurrentDownloads) {
final completer = Completer<void>();
_downloadQueue.add(completer);
await completer.future;
}
_activeDownloads++;
try {
final item = DownloadItem(url, cachePath);
await item.loadCompleter.future;
return item;
} finally {
_activeDownloads--;
if (_downloadQueue.isNotEmpty) {
_downloadQueue.removeFirst().complete();
}
}
}
}
CachedRequest 重复请求位置: lib/models/cached_request.dart:50-100
问题:
class CachedRequest {
_remoteLoad() async {
// ❌ 每次都发起新请求,没有防抖
final response = await http.get(Uri.parse(url));
}
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
if (state == AppLifecycleState.resumed) {
// ❌ 每次恢复都刷新,可能过于频繁
refresh();
}
}
}
优化方案:
class CachedRequest {
DateTime? _lastFetchTime;
static const _minRefreshInterval = Duration(minutes: 5);
Future<void> refresh() async {
// ✅ 防抖:5分钟内不重复请求
if (_lastFetchTime != null &&
DateTime.now().difference(_lastFetchTime!) < _minRefreshInterval) {
_log.info('Skipping refresh, too soon since last fetch');
return;
}
await _remoteLoad();
}
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
if (state == AppLifecycleState.resumed) {
// ✅ 智能刷新:只在数据过期时刷新
final timeSinceLastFetch = _lastFetchTime != null
? DateTime.now().difference(_lastFetchTime!)
: Duration(days: 1);
if (timeSinceLastFetch > Duration(hours: 1)) {
refresh();
}
}
}
}
SharedPreferences 频繁写入位置: lib/persistence/persistence.dart:200-250
问题:
class PreferencesValue<T> {
set value(T v) {
_value = v;
// ❌ 每次赋值都立即写入磁盘
if (_value is bool) {
unawaited(prefs.setBool(key, _value as bool));
}
// ...
}
}
优化方案:
class PreferencesValue<T> {
Timer? _saveTimer;
set value(T v) {
_value = v;
// ✅ 延迟批量写入
_saveTimer?.cancel();
_saveTimer = Timer(Duration(seconds: 1), () {
_saveToPrefs(v);
});
}
void _saveToPrefs(T v) {
if (v is bool) {
unawaited(prefs.setBool(key, v));
}
// ...
}
void dispose() {
_saveTimer?.cancel();
_saveToPrefs(_value); // 立即保存
}
}
// lib/main.dart:180-200
// ❌ 注释掉的旧代码应删除
// ProxyProvider2<SettingsController, ValueNotifier<AppLifecycleState>, AudioController>(
// lazy: false,
// create: (context) => AudioController()..initialize(),
// ...
// ),
// ❌ 生产环境仍然输出大量日志
_log.info('_onPanStart');
_log.info('_onPanUpdate');
// ✅ 使用条件编译
if (kDebugMode) {
_log.info('_onPanStart');
}
| 优先级 | 问题 | 预期收益 | 实施难度 | 预计工时 |
|---|---|---|---|---|
| 🔴 P0 | 主线程阻塞 (main初始化) | ANR -60%, 启动时间 -50% | 中 | 4h |
| 🔴 P0 | 图片解码阻塞 | ANR -40%, 卡顿 -70% | 中 | 6h |
| 🔴 P0 | 内存泄漏 (Image未释放) | Crash -50%, LMK -70% | 低 | 3h |
| 🟡 P1 | AnimationController泄漏 | Crash -20% | 低 | 2h |
| 🟡 P1 | 绘制性能优化 | 流畅度 +30% | 中 | 4h |
| 🟡 P2 | 网络请求限流 | 网络稳定性 +20% | 低 | 2h |
| 🟢 P3 | SharedPreferences优化 | 磁盘IO -50% | 低 | 2h |
总计预计工时: 23小时
预期最终指标:
main() 函数异步初始化// 启动时间监控
class PerformanceMonitor {
static final Stopwatch _startupTimer = Stopwatch();
static void startMonitoring() {
_startupTimer.start();
}
static void reportStartupComplete() {
_startupTimer.stop();
FirebaseHelper.logEvent('app_startup_time', {
'duration_ms': _startupTimer.elapsedMilliseconds,
});
}
}
// 内存监控
class MemoryMonitor {
static void checkMemoryUsage() {
final info = ProcessInfo.currentRss;
if (info > 200 * 1024 * 1024) { // 200MB
FirebaseHelper.logEvent('high_memory_usage', {
'memory_mb': info / (1024 * 1024),
});
}
}
}
优化完成后,请验证以下指标:
报告生成时间: 2024 分析工具: 静态代码分析 + Google Play Console 数据