App 流畅性的关键指标有 UI帧率,GPU帧率,咱们指望它能达到 60fps,也就是16ms每帧。html
为了获取最接近生产环境的数据,咱们应该选择一台尽量低端的真机,而且以 profile 模式或者 release 模式下运行app。android
- 由于 debug 模式会有一些额外的检查工做,好比
assert()
等- 为了加速开发效率,debug 模式是以 JIT(Just in time)模式编译 dart 代码的,而 profile 和 release 是提早编译为机器码 AOT(Ahead Of Time),因此 debug 会慢不少
在 Android Studio and IntelliJ 中, 在菜单栏中点击 Run > Flutter Run main.dart in Profile Mode
git
VS Code:打开 launch.json 文件并设置flutterMode 为 profile:github
"configurations": [
{
"name": "Flutter",
"request": "launch",
"type": "dart",
"flutterMode": "profile" # 测试完后记得把它改回去!
}
]
复制代码
$ flutter run --profile
复制代码
那么检测帧率有哪些方法呢?Flutter 给咱们提供了 Performance Overlay
,以下图,绿色表明当前渲染帧。算法
咱们有三种开启方式json
View > Tool Windows > Flutter Inspector
. 点击下面这个按钮。在 VS Code中 选中 View > Command Palette…
会显示一个 command 面板. 在命令面板中输入 performance
并选择 Toggle Performance Overlay
若是命令显示为不可用,须要检查 app 是否正在运行.redux
从命令行中运行 键盘输入P
浏览器
代码中打开 在MaterialApp
或者 WidgetsApp
的构造函数中设置showPerformanceOverlay
属性为 true
:缓存
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
showPerformanceOverlay: true, // 开启
title: 'My Awesome App',
home: MyHomePage(title: 'My Awesome App'),
);
}
}
复制代码
而后就是动手操做 app,并观察图表上是否出现红色线条。绿色表明当前帧,当页面有变更,图表会不断绘制。蒙版上有2个图表,每一个图表上有三横格,每一个横格表明16ms。若是大多数帧都在第一格,说明达到了指望的帧率。性能优化
图表分别体现了 UI帧率 和 GPU帧率。若是出现了红色,说明对应的线程有太多work要作。那先来了解一下 Flutter 中的4个主要线程分别承担了什么职责。
在运行app的过程当中,观察爆红的地方和触发场景,进行分析。
saveLayer
操做保存为一个图层,最后给这个图层设置透明度。而saveLayer
开销很大,这里官方给出了一个建议:首先确认这些效果是否真的有必要;若是有必要,咱们能够把透明度设置到每一个子控件上,而不是父控件。裁剪操做也是相似。RepaintBoundry
控件中,引擎会自动判断图像是否复杂到须要用repaint boundary,不须要的话也会忽略。MaterialApp(
showPerformanceOverlay: true,
checkerboardOffscreenLayers: true, // 使用了saveLayer的图形会显示为棋盘格式并随着页面刷新而闪烁
checkerboardRasterCacheImages: true, // 作了缓存的静态图片在刷新页面时不会改变棋盘格的颜色;若是棋盘格颜色变了说明被从新缓存了,这是咱们要避免的
...
);
复制代码
在内存优化方面,咱们的目标是但愿减小应用内存占用,减小被系统杀死的几率,同时尽量的避免内存泄露,减小内存碎片化。
Dart 提供了一个性能检测工具Observatory,我在最后一部分会进行详细介绍
性能优化不像其它的开发需求只要完成功能便可,它须要经过统计和数据来证实优化的效果。好比帧率有了多少提升?CPU占用率下降了多少?内存占用减小了多少?对比其它优化策略,哪一个优化效果好?
以检查流畅性为例
scroll_pref.dart
void main() {
enableFlutterDriverExtension();
runApp(const GalleryApp(testMode: true));
}
复制代码
scroll_perf_test.dart
void main() {
group('scrolling performance test', () {
FlutterDriver driver;
setUpAll(() async {
driver = await FlutterDriver.connect();
});
tearDownAll(() async {
if (driver != null)
driver.close();
});
test('measure', () async {
final Timeline timeline = await driver.traceAction(() async {
await driver.tap(find.text('Material'));
final SerializableFinder demoList = find.byValueKey('GalleryDemoList');
for (int i = 0; i < 5; i++) {
await driver.scroll(demoList, 0.0, -300.0, const Duration(milliseconds: 300));
await Future<void>.delayed(const Duration(milliseconds: 500));
}
// Scroll up
for (int i = 0; i < 5; i++) {
await driver.scroll(demoList, 0.0, 300.0, const Duration(milliseconds: 300));
await Future<void>.delayed(const Duration(milliseconds: 500));
}
});
TimelineSummary.summarize(timeline)
..writeSummaryToFile('home_scroll_perf', pretty: true)
..writeTimelineToFile('home_scroll_perf', pretty: true);
});
});
}
复制代码
在命令行下执行如下命令
flutter driver --target=test_driver/scroll_perf.dart
复制代码
这个命令会:
test_driver/
目录下的scroll_perf_test.dart
的测试( flutter drive 能帮你找到带 _test
后缀的同名文件)Test Driver 将会安装 app 到设备上,再跳转到 Material-GalleryDemoList 页面,作5次滑动列表的操做。执行完成后会借助 TimelineSummary
,在build目录下生成两个json文件:home_scroll_perf.timeline.json
和home_scroll_perf.timeline_summary.json
。这里咱们看一下timeline_summary.json
文件的内容
{
"average_frame_build_time_millis": 5.6319655172413805, # 平均每帧 build 时间
"90th_percentile_frame_build_time_millis": 10.216,
"99th_percentile_frame_build_time_millis": 17.168,
"worst_frame_build_time_millis": 20.415, # 最长帧 build 时间
"missed_frame_build_budget_count": 21, # build 期丢帧数
"average_frame_rasterizer_time_millis": 14.234294964028772, # 平均每帧光栅化时间
"90th_percentile_frame_rasterizer_time_millis": 22.338,
"99th_percentile_frame_rasterizer_time_millis": 42.661,
"worst_frame_rasterizer_time_millis": 43.161,
"missed_frame_rasterizer_budget_count": 112,
"frame_count": 116,
"frame_build_times": [
...
],# 全部帧的 build 时间
"frame_rasterizer_times": [
...
] # 全部帧的光栅化时间
}
复制代码
更多能够参考官方文档
Observatory 是用于分析和调试Dart应用程序的工具。Observatory容许您根据须要查看正在运行的Dart虚拟机(VM),并提供实时,即时的数据报告。您可使用它来浏览应用程序的不少状态。
有2种方式:
Flutter Inspector
面板,点击小闹钟图标,以下图
flutter run
,应用启动成功后,命令行中会输出一个 url,把 url copy 到浏览器便可。
打开Observatory面板,要先选择isolate,表示当前应用。
下面是性能优化常关注的几个页面。
app的时间都花在哪了?
进入这个页面后要通常需加载个几秒钟,so be patient。图表的下部按cpu占用比例作了一个列表,反映的是函数的调用次数和执行时间(划重点)。通常排在前面的函数(这些函数是?有待学习)都不是咱们写的dart代码。若是你发现本身的某个函数调用占比反常,那么可能存在问题。
注:flutter程序的cpu profile和官方文档上的数据展现不太同样,没有VM tags,因此对于百分比的具体含义有待研究。
采样过程:它每隔必定时间对isolate作采样,采样的数据存储在一个环形缓冲区(叫作profile),它能存放约2分钟的数据,一旦缓冲区满了,它会用最新的sample替换掉最旧的。
内存都被谁吃了?
Heap 堆,动态分配的Dart对象所在的内存空间
经过这个面板你能看到新生代/老生代的内存大小和占比;每一个类型所占用的内存大小。
为了debug的方便,咱们能够获取到某段时间的内存分配状况:点击Reset Accumulator按钮,把数据清零,执行一下要测试的程序,点击刷新。
为了检查内存泄露,咱们能够点击GC按钮,手动执行GC。
Accumulator Size:自点击Reset Accumulator以来,累加对象占用内存大小 Accumulator Instances:自点击Reset Accumulator以来,累加实例个数 Current Size:当前对象占用内存大小 Current Instances:当前对象数量
是否出现内存碎片化
heap map 面板能查看old generation中的内存状态
它以颜色显示内存块。 每一个内存页面(page of memory)为256 KB,每页由水平黑线分隔。 像素的颜色表示对象的类ID - 例如,蓝色表示字符串,绿色表示双精度表。 可用空间为白色,指令(代码)为紫色。 若是启动垃圾收集(使用“分配配置文件”屏幕中的GC按钮),堆映射中将显示更多空白区域(可用空间)。 将光标悬停在上面时,顶部的状态栏显示有关光标下像素所表明的对象的信息。 显示的信息包括该对象的类型,大小和地址。 当你看到白色区域中有不少分散的其它颜色,说明存在内存碎片化,多是内存泄露致使的。
知道哪些代码执行了,哪些没有执行
应用场景:写某个类的单元测试,跑完测试后,能够查看哪些代码没有覆盖到,进而补全
查看某个实例的状态,好比咱们的项目中使用了Flutter_redux,页面的展现来源与状态树,当页面出现了非预期的效果,咱们能够经过Observatory查看状态树
性能优化涉及了应用的方法面面,很难一言以蔽之。本文咱们主要讨论了性能优化的两大主题 —— 流畅性和内存优化,并分别介绍了他们的检测方法和优化策略。另外,咱们在优化的同时也要增强优化的证实,用数听说话。最后,我强烈推荐你们尝试一下 Observatory 这个工具,开发中若是遇到了奇怪的问题,没准它能帮你找到答案。
本文版权属于再惠研发团队,欢迎转载,转载请保留出处。@akindone