跳转到正文
YGJoon 技术博客

Flutter 性能测试详解

发布于:
·1.5k 字 · 约 4 分钟·作者:
flutter_driver 连真机采集 Timeline:traceAction 量启动耗时与列表滚动帧率,TimelineSummary 直接出报告
Flutter 性能测试详解文章封面
目录

简介

性能测试用于评估应用的性能表现。本文介绍如何用 Timeline 采集启动耗时、滚动帧率等指标,并生成性能报告。

版本提示:下文使用 flutter_driver(test_driver/ + flutter drive 工作流)。该包已被官方废弃,新项目请使用 integration_test(integration_test/ 目录 + flutter test integration_test);flutter_driver 的 API 在旧版本 SDK 上仍可运行,TimelineSummary 等概念与新版相通。

基本配置

添加依赖

dev_dependencies:
  flutter_test:
    sdk: flutter
  flutter_driver:
    sdk: flutter
  test: ^1.24.1

创建测试文件

// test_driver/app.dart
import 'package:flutter_driver/driver_extension.dart';
import 'package:my_app/main.dart' as app;

void main() {
  enableFlutterDriverExtension();
  app.main();
}

// test_driver/app_test.dart
import 'package:flutter_driver/flutter_driver.dart';
import 'package:test/test.dart';

void main() {
  group('Performance Tests', () {
    late FlutterDriver driver;

    setUpAll(() async {
      driver = await FlutterDriver.connect();
    });

    tearDownAll(() async {
      await driver.close();
    });

    // 测试用例
  });
}

测试方法

1. 启动时间测试

test('Measure app startup time', () async {
  final timeline = await driver.traceAction(() async {
    await driver.waitUntilFirstFrameRasterized();
  });
  
  final summary = TimelineSummary.summarize(timeline);
  print('Startup time: ${summary.frameBuildTime}ms');
});

2. 滚动性能测试

test('Measure scroll performance', () async {
  // 定位滚动列表
  final listFinder = find.byType('ListView');
  
  final timeline = await driver.traceAction(() async {
    // 执行滚动操作
    await driver.scroll(
      listFinder,
      0,
      -300,
      Duration(milliseconds: 500),
    );
  });
  
  final summary = TimelineSummary.summarize(timeline);
  print('Average frame build time: ${summary.averageFrameBuildTime}ms');
  print('Worst frame build time: ${summary.worstFrameBuildTime}ms');
  print('Frame count: ${summary.frameCount}');
});

完整示例

void main() {
  group('Performance Tests', () {
    late FlutterDriver driver;
    
    setUpAll(() async {
      driver = await FlutterDriver.connect();
      
      // 等待应用稳定
      await driver.waitUntilFirstFrameRasterized();
    });
    
    tearDownAll(() async {
      await driver.close();
    });
    
    test('Measure app startup performance', () async {
      final timeline = await driver.traceAction(() async {
        await driver.waitFor(find.text('Welcome'));
      });
      
      final summary = TimelineSummary.summarize(timeline);
      
      // 输出性能数据
      print('===== App Startup Performance =====');
      print('Frame build time: ${summary.frameBuildTime}ms');
      print('Frame rasterizer time: ${summary.frameRasterizerTime}ms');
      
      // 保存跟踪文件
      await summary.writeTimelineToFile(
        'startup_timeline',
        pretty: true,
      );
    });
    
    test('Measure scrolling performance', () async {
      // 等待列表加载
      await driver.waitFor(find.byType('ListView'));
      
      final timeline = await driver.traceAction(() async {
        // 执行多次滚动
        for (var i = 0; i < 5; i++) {
          await driver.scroll(
            find.byType('ListView'),
            0,
            -300,
            Duration(milliseconds: 500),
          );
          
          await Future.delayed(Duration(milliseconds: 500));
        }
      });
      
      final summary = TimelineSummary.summarize(timeline);
      
      print('===== Scrolling Performance =====');
      print('Average frame build time: ${summary.averageFrameBuildTime}ms');
      print('90th percentile frame build time: '
          '${summary.computePercentileFrameBuildTime(90.0)}ms');
      print('Worst frame build time: ${summary.worstFrameBuildTime}ms');
      print('Frame count: ${summary.frameCount}');
      print('Frame build time histogram: ${summary.buildTimeHistogram}');
      
      // 保存跟踪文件
      await summary.writeTimelineToFile(
        'scrolling_timeline',
        pretty: true,
      );
    });
    
    test('Measure memory usage', () async {
      final memory = await driver.getVMStatistics();
      
      print('===== Memory Usage =====');
      print('Used heap size: ${memory['heapUsed']} bytes');
      print('Total heap size: ${memory['heapTotal']} bytes');
      print('External memory: ${memory['external']} bytes');
    });
  });
}

性能指标

1. 时间指标

2. 内存指标

3. CPU 指标

性能分析工具

1. DevTools

// 启用 DevTools 性能叠加层
void main() {
  if (kDebugMode) {
    debugPaintSizeEnabled = true;
    debugPrintMarkNeedsLayoutStacks = true;
    debugPrintMarkNeedsPaintStacks = true;
  }
  runApp(MyApp());
}

2. Performance Overlay

MaterialApp(
  showPerformanceOverlay: true,
  home: MyHomePage(),
)

最佳实践

  1. 先定基线再优化:同一台真机、同样电量和发热状态下跑 3 次取中位数作为基线。不同手机的帧率基线完全不同(实战案例里说的),拿千元机的数字跟旗舰机的比没有意义。
  2. 只盯 90 分位和最差帧:平均帧构建时间会被大量空闲帧拉低——列表滚动时 1 帧 200ms 的卡顿在平均值里根本看不见。computePercentileFrameBuildTime(90.0) 和 worstFrameBuildTime 才是用户体感。
  3. traceAction 的范围尽量小:只包住”滚动 500ms”这个动作(正文示例),别把”等页面加载+滚动+返回”全包进一个 timeline——混在一起出问题定位不到是哪一段。
  4. 用 release 模式测性能:debug 模式的帧时间没有参考价值(JIT、断言全在拖慢)。flutter drive --release 或 integration_test 打 release 包——这是正文示例没写但必须做的一步。
  5. 性能测试进 CI 但别卡门:帧率数字抖动大(实战案例),直接设硬阈值会天天误报。做法:CI 只记录趋势(存 timeline 文件),人工 review 跌幅;等基线稳定了再谈卡点。

注意事项

  1. 测试环境要固定:关掉后台应用、同一台设备、最好插电。手机一发热降频,帧时间直接翻倍——你以为是代码退化,其实是手机烫了。
  2. flutter_driver 已废弃:正文用的 flutter_driver 在新 SDK 上跑不起来,新项目用 integration_test(开头版本提示已写)。TimelineSummary 的概念相通,API 照着官方文档迁移。
  3. 别在模拟器上测性能:x86 模拟器的 GPU 是宿主机混渲,帧时间跟真机完全两个世界。模拟器只验证”能跑”,性能数字只认真机。
  4. 采样别太贪:writeTimelineToFile(pretty: true) 的 timeline 文件一次滚动就能几十 MB。CI artifact 只保留 summary 的几个关键数字,原始文件按需下载。
  5. 内存测试看趋势不看绝对值:getVMStatistics 的 heapUsed 一次快照说明不了泄漏——要的是”重复进出页面 20 次后 heap 是否持续上涨”。单次数字正常、趋势上涨才是泄漏。

实战案例

性能测试(自动化跑帧率、内存基线)我还没在项目里做过。我的性能工作全是手动的:DevTools Performance 视图看 raster/UI 线程耗时,flutter_travel 列表卡顿时定位到图片解码,一次解决。自动化性能测试的门槛在于基线管理——不同手机的帧率基线完全不同,CI 跑出来的数字抖动很大,误报比漏报还烦人。小团队我的建议:先把 flutter test 的 widget 测试跑起来(电商奢侈品专区改版 就是这么做的),性能靠 DevTools + 真机走查。等团队有专人看质量了,再考虑集成 integration_test 的性能采集。参考:Flutter 官方文档性能测试章节。

总结

性能测试是保证应用性能的重要手段。通过合理的性能测试和监控,可以及时发现和解决性能问题,提供更好的用户体验。


分享这篇文章:


相关文章

上一篇
Flutter 性能监控:FrameTiming、RSS 与 DevTools 实战
下一篇
Flutter 实现自定义时间选择器