软件项目的交付是一个复杂且漫长的过程,任何细小的失误都有可能致使交付过程失败。在软件开发过程当中,除了代码逻辑的 Bug 和视觉异常这些功能层面的问题以外,移动应用另外一类常见的问题是性能问题,好比滑动操做不流畅、页面出现卡顿丢帧现象等。这些问题虽然不至于让移动应用彻底不可用,但也很容易引发用户反感,从而对应用质量产生质疑,甚至失去耐心。前端
那么,对于应用渲染并不流畅,出现了性能问题,咱们该如何检测,又该从哪里着手处理呢?和移动开发相似, Flutter 的性能问题主要能够分为 GPU 线程问题和 UI 线程(CPU)问题两类。对于这些问题,有一个通用的套路:首先,都须要先经过性能图层进行初步分析,而一旦确认问题存在,接下来就是利用 Flutter 提供的各种分析工具来进行问题定位。android
Debug模式能够在真机和模拟器上同时运行,此模式会打开全部的断言,包括debugging信息、debugger aids(好比observatory)和服务扩展。优化了快速develop/run循环,可是没有优化执行速度、二进制大小和部署。命令flutter run就是以这种模式运行的,经过sky/tools/gn --android
或者sky/tools/gn --ios
来构建应用的。ios
Release模式只能在真机上运行,不能在模拟器上运行:会关闭全部断言和debugging信息,关闭全部debugger工具。优化了快速启动、快速执行和减少包体积。禁用全部的debugging aids和服务扩展。这个模式是为了部署给最终的用户使用。命令flutter run --release
就是以这种模式运行的,经过sky/tools/gn --android --runtime-mode=release
或者sky/tools/gn --ios --runtime-mode=release
来构建应用。算法
Profile模式只能在真机上运行,不能在模拟器上运行,基本和Release模式一致,除了启用了服务扩展和tracing,以及一些为了最低限度支持tracing运行的东西(好比能够链接observatory到进程)。命令flutter run --profile
就是以这种模式运行的,经过sky/tools/gn --android --runtime-mode=profile
或者sky/tools/gn --ios --runtime-mode=profile
来构建应用。编程
headless test模式只能在桌面上运行,基本和Debug模式一致,除了是headless的并且你能在桌面运行。命令flutter test
就是以这种模式运行的,经过sky/tools/gn
来build。缓存
在实际开发中,应该用到上面所说的四种模式又各自分为两种:一种是未优化的模式,供开发人员调试使用;一种是优化过的模式,供最终的开发人员使用。默认状况下是未优化模式,若是要开启优化模式,build的时候在命令行后面添加--unoptimized参数。bash
无论是移动开发仍是前端开发,对于性能问题分析的思路都是先分析并定位问题,Flutter也不例外,借助Flutter 提供的度量性能工具,咱们能够快速定位代码中的性能问题,而性能图层就是帮助咱们确认问题影响范围的利器,它相似Android的图层分析工具。网络
为了使用性能图层,Flutter提供了分析(Profile)模式,与调试代码能够经过模拟器在调试模式下找到代码逻辑 Bug 不一样,性能问题须要在发布模式下使用真机进行检测。相比发布(Release)模式而言,调试模式增长了不少额外的检查(好比断言),这些检查可能会耗费不少资源;更重要的是,调试模式使用 JIT (即时编译)模式运行应用,代码执行效率较低。这就使得调试模式运行的应用,没法真实反映出它的性能问题。并发
而另外一方面,模拟器使用的指令集为 x86,而真机使用的指令集是 ARM,因为这两种方式的二进制代码执行行为彻底不一样,所以模拟器与真机的性能差别较大。一些 x86 指令集擅长的操做模拟器会比真机快,而另外一些操做则会比真机慢,这也使得咱们没法使用模拟器来评估真机才能出现的性能问题。app
为了调试性能问题,咱们须要在发布模式的基础之上,为分析工具提供少许必要的应用追踪信息,这就是分析模式。除了一些调试性能问题必须的追踪方法以外,Flutter 应用的分析模式和发布模式的编译和运行是相似的,只是启动参数变成了 profile 而已。咱们能够在 Android Studio 中经过菜单栏点击 【Run】-【Profile 】‘main.dart’ 选项启动应用,也能够经过命令行参数 flutter run --profile
运行 Flutter 应用。
在完成了应用启动以后,接下来咱们就能够利用 Flutter 提供的渲染问题分析工具,即性能图层(Performance Overlay)来分析渲染问题了。性能图层会在当前应用的最上层,以 Flutter 引擎自绘的方式展现 GPU 与 UI 线程的执行图表,而其中每一张图表都表明当前线程最近 300 帧的表现,若是 UI 产生了卡顿(跳帧),这些图表能够帮助咱们分析并找到缘由,以下图所示。
同时,为了保持 60Hz 的刷新频率,GPU 线程与 UI 线程中执行每一帧耗费的时间都应该小于 16ms(1/60 秒)。在这其中有一帧处理时间过长,就会致使界面卡顿,图表中就会展现出一个红色竖条,以下图所示。
GPU渲染问题主要集中在底层渲染耗时上,有时候 Widget 树虽然构造起来容易,但在 GPU 线程下的渲染却很耗时。例如,涉及 Widget 裁剪、蒙层这类多视图叠加渲染,或是因为缺乏缓存致使静态图像的反复绘制,都会明显拖慢 GPU 的渲染速度。
接下来,使用性能图层提供的两项参数,即检查多视图叠加的视图渲染开关 checkerboardOffscreenLayers和检查缓存的图像开关checkerboardRasterCacheImages来检查这两种状况。
多视图叠加一般会用到 Canvas 里的 savaLayer 方法,这个方法在实现一些特定的效果(好比半透明)时很是有用,但因为其底层实现会在 GPU 渲染上涉及多图层的反复绘制,所以会带来较大的性能问题。
对于 saveLayer 方法使用状况的检查,咱们只须要在 MaterialApp 的初始化方法中,将 checkerboardOffscreenLayers 开关设置为 true,分析工具就会自动帮咱们检测多视图叠加的状况。使用了 saveLayer 的 Widget 会自动显示为棋盘格式,并随着页面刷新而闪烁。不过,saveLayer 是一个较为底层的绘制方法,所以咱们通常不会直接使用它,而是会经过一些功能性 Widget,在涉及须要剪切或半透明蒙层的场景中间接地使用。因此一旦遇到这种状况,咱们须要思考一下是否必定要这么作,能不能经过其余方式来实现呢?
好比下面的例子中,咱们使用 CupertinoPageScaffold 与 CupertinoNavigationBar 实现了一个动态模糊的效果,代码以下:
CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(),//动态模糊导航栏
child: ListView.builder(
itemCount: 100,
//为列表建立100个不一样颜色的RowItem
itemBuilder: (context, index)=>TabRowItem(
index: index,
lastItem: index == 100 - 1,
color: colorItems[index],//设置不一样的颜色
colorName: colorNameItems[index],
)
)
);
复制代码
其中,动态模糊的NavigationBar效果以下图所示。
Scaffold(
//使用普通的白色AppBar
appBar: AppBar(title: Text('Home', style: TextStyle(color:Colors.black),),backgroundColor: Colors.white),
body: ListView.builder(
itemCount: 100,
//为列表建立100个不一样颜色的RowItem
itemBuilder: (context, index)=>TabRowItem(
index: index,
lastItem: index == 100 - 1,
color: colorItems[index],//设置不一样的颜色
colorName: colorNameItems[index],
)
),
);
复制代码
运行一下代码,能够看到,在去掉了动态模糊效果以后,GPU 的渲染压力获得了缓解,checkerboardOffscreenLayers 检测图层也再也不频繁闪烁了。
从资源的角度看,另外一类很是消耗性能的操做是渲染图像,由于图像渲染会涉及 I/O、GPU 存储以及不一样通道的数据格式转换,所以渲染过程的构建须要消耗大量资源。为了缓解 GPU 的压力,Flutter 提供了多层次的缓存快照,这样 Widget 重建时就无需从新绘制静态图像了。
与检查多视图叠加渲染的 checkerboardOffscreenLayers 参数相似,Flutter 提供了检查缓存图像的开关 checkerboardRasterCacheImages,来检测在界面重绘时频繁闪烁的图像。
为了提升静态图像显示性能,咱们能够把须要静态缓存的图像加到 RepaintBoundary 中,RepaintBoundary 能够肯定 Widget 树的重绘边界,若是图像足够复杂,Flutter 引擎会自动将其缓存,从而避免重复刷新。固然,由于缓存资源有限,若是引擎认为图像不够复杂,也可能会忽略 RepaintBoundary。下面的代码展现了经过 RepaintBoundary,将一个静态复合 Widget 加入缓存的具体用法,以下所示。
RepaintBoundary(//设置静态缓存图像
child: Center(
child: Container(
color: Colors.black,
height: 10.0,
width: 10.0,
),
));
复制代码
若是说 GPU 线程问题定位的是渲染引擎底层渲染异常,那么 UI 线程问题发现的则是应用的性能瓶颈。好比在视图构建时,在 build 方法中使用了一些复杂的运算,或是在主 Isolate 中进行了同步的 I/O 操做。这些问题,都会明显增长 CPU 的处理时间,拖慢应用的响应速度。
针对这类问题,咱们可使用 Flutter 提供的 Performance 工具,来记录应用的执行轨迹。Performance 是一个强大的性能分析工具,可以以时间轴的方式展现 CPU 的调用栈和执行时间,去检查代码中可疑的方法调用。
打开 Android Studio 底部工具栏中的“Open DevTools”按钮以后,系统会自动打开 Dart DevTools 的网页,将顶部的 tab 切换到 Performance 后,咱们就能够开始分析代码中的性能问题了。
class MyHomePage extends StatelessWidget {
MyHomePage({Key key}) : super(key: key);
String generateMd5(String data) {
//MD5固定算法
var content = new Utf8Encoder().convert(data);
var digest = md5.convert(content);
return hex.encode(digest.bytes);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('demo')),
body: ListView.builder(
itemCount: 30,// 列表元素个数
itemBuilder: (context, index) {
//反复迭代计算MD5
String str = '1234567890abcdefghijklmnopqrstuvwxyz';
for(int i = 0;i<10000;i++) {
str = generateMd5(str);
}
return ListTile(title: Text("Index : $index"), subtitle: Text(str));
}// 列表项建立方法
),
);
}
}
复制代码
与性能图层可以自动记录应用执行状况不一样,使用 Performance 来分析代码执行轨迹,咱们须要手动点击【Record】按钮去主动触发,在完成信息的抽样采集后再点击【Stop】按钮结束录制,而后就能够获得在这期间应用的执行状况了。
Performance 记录的应用执行状况叫作 CPU 帧图,又被称为火焰图。火焰图是基于记录代码执行结果所产生的图片,用来展现 CPU 的调用栈,表示的是 CPU 的繁忙程度。因此,咱们要检测 CPU 耗时问题,皆能够查看火焰图底部的哪一个函数占据的宽度最大。只要有“平顶”,就表示该函数可能存在性能问题,以下图所示。
在 Flutter 中,性能分析过程能够分为 GPU 线程问题定位和 UI 线程(CPU)问题定位,而它们都须要在真机上以分析模式(Profile)启动应用,并经过性能图层分析大体的渲染问题范围。 一旦确认问题存在,接下来就须要利用 Flutter 所提供的分析工具来定位问题缘由了。关于 GPU 线程渲染问题,咱们能够重点检查应用中是否存在多视图叠加渲染,或是静态图像反复刷新的现象。而 UI 线程渲染问题,咱们则是经过 Performance 工具记录的火焰图(CPU 帧图),分析代码耗时来找出应用执行瓶颈。
总的来讲,因为 Flutter 采用基于声明式的 UI 设计理念,以数据驱动渲染,并采用 Widget->Element->RenderObject 三层结构,屏蔽了无谓的界面刷新,可以保证绝大多数状况下咱们构建的应用都是高性能的,因此在使用分析工具检测出性能问题以后,一般咱们并不须要作太多的细节优化工做,只须要在改造过程当中避开一些常见的坑,就能够得到优异的性能。同时,为了不形成性能问题,还应该从如下几个方面着手:
参考资料
1,Flutter 应用程序调试
2,Flutter For Web入门实战
3,Flutter开发之路由与导航
4,Flutter 必备开源项目
5,Flutter混合开发
6,Flutter的Hot Reload是如何作到的
7,《Flutter in action》开源
8,Flutter开发之JSON解析
9,Flutter开发之基础Widgets
10,Flutter开发之导航与路由管理
11,Flutter开发之网络请求
12,Flutter基础知识
13,Flutter开发之Dart语言基础
14,Flutter入门与环境搭建
15,移动跨平台方案对比:WEEX、React Native、Flutter和PWA
16,Flutter开发之异步编程
17,构建属于本身的Flutter混合开发框架
18,Flutter应用集成极光推送
19,Flutter 国际化适配实战
20,Apple为何不封杀 Flutter,之后会封杀吗