-
从命令行中运行 键盘输入
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个主要线程分别承担了什么职责。
- Platform线程:插件代码运行的线程;即Android/iOS的主线程,
- UI线程:在Dart虚拟机中执行Dart代码。作用是创建视图树,然后将它发送给GPU。注意不要阻塞此线程!
- GPU线程:把上面提到的视图树渲染出来,虽然我们在flutter中不能直接访问GPU线程和数据,但是Dart代码可能导致此线程变慢
- I/O线程:执行比较耗时的任务
在运行app的过程中,观察爆红的地方和触发场景,进行分析。
分析思路
-
如果是UI报红:那么可能是执行了某个较耗时的函数?或者函数调用过多?算法复杂度高?
-
如果只是 GPU 报红:那么可能是要绘制的图形过于复杂?或者执行了过多GPU操作?
-
比如要实现一个混合图层的半透明效果:如果把透明度设置在顶层控件上,CPU会把每个子控件图层渲染出来,再执行
saveLayer
操作保存为一个图层,最后给这个图层设置透明度。而saveLayer
开销很大,这里官方给出了一个建议:首先确认这些效果是否真的有必要;如果有必要,我们可以把透明度设置到每个子控件上,而不是父控件。裁剪操作也是类似。 -
还有一个拖慢GPU渲染速度的是没有给静态图像做缓存,导致每次build都会重新绘制。我们可以把静态图形加到
RepaintBoundry
控件中,引擎会自动判断图像是否复杂到需要用repaint boundary,不需要的话也会忽略。 -
开启saveLayer和图形缓存的检查
MaterialApp(
showPerformanceOverlay: true,
checkerboardOffscreenLayers: true, // 使用了saveLayer的图形会显示为棋盘格式并随着页面刷新而闪烁
checkerboardRasterCacheImages: true, // 做了缓存的静态图片在刷新页面时不会改变棋盘格的颜色;如果棋盘格颜色变了说明被重新缓存了,这是我们要避免的
…
);
提高流畅性的策略
- 代码调用时机是否可以延后?如底部导航栏式的页面,没有必要第一次进入就把每个子Page都创建出来
- 尽量做到局部刷新
- 把耗时的计算放到独立的isolate去执行
- 检查不必要的 saveLayer
- 检查静态图片是否添加缓存
- relayout boundary:参考
- repaint boundary:参考
内存优化
在内存优化方面,我们的目标是希望减少应用内存占用,减少被系统杀死的概率,同时尽可能的避免内存泄露,减少内存碎片化。
内存优化策略
- 加载对象过大?如图片质量和尺寸不做限制就加载
- 加载对象过多?如加载长列表;在调用频率很高的方法中创建对象
- 合理设置缓存大小/长度
- 在内存不足时或离开页面时清空缓存数据
- 使用ListView.build()来复用子控件
- 自定义绘图中避免在onDraw中做创建对象操作,或者相同的参数设置
- 复用系统提供的资源,比如字符串、图片、动画、样式、颜色、简单布局,在应用中直接引用
- 内存泄露的问题?比如dispose需要销毁的listener等
- 不可见的视图是否也在build?
- 页面离开后的网络请求是否取消?
如何获取内存状态
Dart 提供了一个性能检测工具Observatory,我在最后一部分会进行详细介绍
优化证明
优化证明的意义
性能优化不像其它的开发需求只要完成功能即可,它需要通过统计和数据来证明优化的效果。比如帧率有了多少提高?CPU占用率降低了多少?内存占用减少了多少?对比其它优化策略,哪个优化效果好?
优化证明的流程
举个例子
以检查流畅性为例
1. 在profile模式下运行并开启Performance Overlay,整体测试app
2. 找到帧率报红色的模块
3. 把页面孤立出来,并多次测量,并得到baseline(参照)帧率数据。比如长列表页面出现了卡顿,我们可以用TestDriver写一个ListView滑动的性能测试(更多参考Flutter gallery)
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.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.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
这个命令会:
- build 目标 app,并把它安装到设备上
- 运行位于
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”: [
…
] # 所有帧的光栅化时间
}
4. 优化
5. 用步骤3的方法再次测量,对比baseline得出确切的优化效果
Flutter 提供的性能调试 API
更多可以参考官方文档
性能检测利器 Observatory
Observatory 是用于分析和调试Dart应用程序的工具。Observatory允许您根据需要查看正在运行的Dart虚拟机(VM),并提供实时,即时的数据报告。您可以使用它来浏览应用程序的很多状态。
打开Observatory
有2种方式:
- 在 androidStudio 中打开
Flutter Inspector
面板,点击小闹钟图标,如下图
- 再命令行中运行
flutter run
,应用启动成功后,命令行中会输出一个 url,把 url copy 到浏览器即可。
打开Observatory面板,要先选择isolate,表示当前应用。
主要页面
下面是性能优化常关注的几个页面。
1. CPU Profile
app的时间都花在哪了?
进入这个页面后要一般需加载个几秒钟,so be patient。图表的下部按cpu占用比例做了一个列表,反映的是函数的调用次数和执行时间(划重点)。一般排在前面的函数(这些函数是?有待学习)都不是我们写的dart代码。如果你发现自己的某个函数调用占比反常,那么可能存在问题。
注:flutter程序的cpu profile和官方文档上的数据展示不太一样,没有VM tags,所以对于百分比的具体含义有待研究。
采样过程:它每隔一定时间对isolate做采样,采样的数据存储在一个环形缓冲区(叫做profile),它能存放约2分钟的数据,一旦缓冲区满了,它会用最新的sample替换掉最旧的。
- Profile contains:采样时长和对应的采样数
- Sampling:采样频率,默认1000Hz,即每毫秒采样一次
2. Allocation Profile
内存都被谁吃了?
Heap 堆,动态分配的Dart对象所在的内存空间
- New generation: 新创建的对象,一般来说对象比较小,生命周期短,如local 变量。在这里GC活动频繁
- Old generation:从GC中存活下来的New generation将会提拔到老生代Old generation,它比新生代空间大,更适合大的对象和生命周期长的对象
尾声
最后,我再重复一次,如果你想成为一个优秀的 Android 开发人员,请集中精力,对基础和重要的事情做深度研究。
对于很多初中级Android工程师而言,想要提升技能,往往是自己摸索成长,不成体系的学习效果低效漫长且无助。 整理的这些架构技术希望对Android开发的朋友们有所参考以及少走弯路,本文的重点是你有没有收获与成长,其余的都不重要,希望读者们能谨记这一点。
最后想要拿高薪实现技术提升薪水得到质的飞跃。最快捷的方式,就是有人可以带着你一起分析,这样学习起来最为高效,所以为了大家能够顺利进阶中高级、架构师,我特地为大家准备了一套高手学习的源码和框架视频等精品Android架构师教程,保证你学了以后保证薪资上升一个台阶。
当你有了学习线路,学习哪些内容,也知道以后的路怎么走了,理论看多了总要实践的。
进阶学习视频
附上:我们之前因为秋招收集的二十套一二线互联网公司Android面试真题 (含BAT、小米、华为、美团、滴滴)和我自己整理Android复习笔记(包含Android基础知识点、Android扩展知识点、Android源码解析、设计模式汇总、Gradle知识点、常见算法题汇总。)
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门,即可获取!
收集的二十套一二线互联网公司Android面试真题** (含BAT、小米、华为、美团、滴滴)和我自己整理Android复习笔记(包含Android基础知识点、Android扩展知识点、Android源码解析、设计模式汇总、Gradle知识点、常见算法题汇总。)
[外链图片转存中…(img-TdVklotn-1714812546100)]
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门,即可获取!