Flutter、Golang、Python、编译原理、算法、Chrome原理学习系列文章抢先看请关注【码农帮派】:
在Flutter中性能问题可以分为GPU线程问题、UI线程(CPU)问题。这两类问题可以通过Flutter提供的性能图层进行定位分析。
性能图层(Performance Overlay)
Flutter为了帮助开发者定位代码中的性能问题,提供了性能图层,它可以让我们快速的定位问题。
为了使用性能图层,我们需要以分析模式(Profile)来运行App,之前我们已经了解到Profile(分析)模式和Release模式一样,都需要在真机上才能运行,而且Profile模式和Release模式一样,使用AOT编译方式,去掉了诸如代码断言等额外的检查,这样才能在真实的用户体验环境中监测App的性能开销。
另一方面,模拟器使用的x86的指令集,而真机使用的ARM指令集。这两种指令集的二进制代码执行行为完全不同,因此真机和模拟器性能监控也是有差异的。
Profile(分析)模式是在Release(发布)模式的基础上,为分析工具提供了少量必要的应用追踪信息。在Android Studio上运行Profile模式:
也可以使用指令来运行Profile模式:
// 以Profile模式运行App
flutter run --profile
界面渲染分析
通过Profile模式运行App之后,我们就可以使用Flutter提供的性能图层(Performance Overlay)来分析界面渲染性能问题。
性能图层会在当前应用的最上层,以Flutter引擎自绘的方式展示GPU和UI线程(CPU)的执行图表,每张图都代表了对应线程最近300帧的渲染情况。
上面的图就是性能图层,GPU渲染性能图表是上面,UI线程(CPU)渲染的性能图表在下面,其中绿色的条代表当前帧。
界面刷新的频率一般不能小于60Hz,为了保证60Hz的刷新频率,GPU线程和UI线程中执行每一帧的时间不能超过16ms(1/60秒)。要是某一帧的处理时间过长,就会产生界面卡顿的情况,下图中红色的条就表示界面的一次卡顿:
如果红色条出现在GPU线程图标,就说明渲染的图形过于复杂,导致无法快速的渲染;如果红色条出现在UI线程的图表,就说明Dart在主线程中执行了耗时的任务,消耗了大量的资源,这时我们就需要优化代码,或将耗时的操作放到异步线程中进行。
GPU问题定位
GPU的渲染问题主要是底层渲染耗时。有时候Widget树的构建很简单,但是GPU线程的渲染却很耗时。比如:
(1)当GPU渲染涉及到Widget的裁剪、蒙层这类多视图叠加的渲染;
(2)缺少缓存导致静态图像反复绘制,也会导致GPU渲染线程的速度。
Flutter提供了两个参数开关:
checkerboardOffscreenLayers检查多视图叠加渲染性能;
checkerboardRasterCacheImages检查缓存图片性能。
checkerboardOffscreenLayers
多视图叠加通常会用到Canvas里面的saveLayer方法,这个方法可以用来实现一些特定的效果(比如半透明),但是由于saveLayer方法的底层实现会在GPU渲染上对多图层进行反复绘制,因此带来较大的性能开销。
针对saveLayer性能的监测,我们需要在MaterialApp的初始化方法中传入checkerboardOffscreenLayers参数,并设置为true,这样性能监测工具就会帮助我们监测多视图叠加的情况,会将使用saveLayer的Widget显示为网格状,并会随着界面的刷新而闪动。
我们需要明白的是,saveLayer是一个底层方法,我们并不会直接使用它,我们往往是通过一些功能组件间接的使用到了saveLayer方法。
下面的代码使用了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如下:
当我们开启checkerboardOffscreenLayers之后,可以看到视图蒙层效果对GPU的渲染压力导致性能视图频繁闪动:
checkerboardRasterCacheImages
另外一个比较消耗资源的操作是图像的渲染,这是因为图像渲染涉及到I/O、GPU操作、以及不同通道的数据格式转换,因此图像的渲染会占用比较多的系统资源。Flutter为了缓解GPU压力,提供了多层缓存快照,这样Widget重建时就无需重复绘制静态图像了。
同样的,我们可以在MaterialApp中开启checkerboardRasterCacheImags参数,来检测界面重绘是静态图像缓存状态。
为了提高静态图像显示性能,我们可以将需要缓存的静态图像放到RepaintBoundary中,RepaintBoundary可以确定Widget树的重绘边界,如果图像足够复杂,Flutter引擎会自动进行缓存,避免重复绘制。当然,因为缓存资源有限,如果Flutter引擎认为当前的Widget不够复杂,就会忽略RepaintBoundary。
下面的代码展示了RepaintBoundary的使用方法:
RepaintBoundary(
child: Center(
child: Container(
color: Colors.black,
height: 10.0,
width: 10.0,
),
));
UI线程问题定位
GPU线程问题的定位涉及到底层渲染性能的优化,UI线程问题的定位就是因为我们在开发过程中,在主Isolate中存在比较耗时的代码,导致界面卡顿,比如build方法中进行复杂的运算、在主Isolate中同步执行耗时的I/O操作等等,这些都会增加CPU处理的时间,拖慢App响应速度。
针对UI线程的问题,Flutter提供了Performance工具进行定位,它记录了App执行的轨迹信息。Performance能够以时间轴的方式展示CPU的调用栈和执行情况,以此分析应用性能。
在Android Studio中找到 “Open DevTools“,点击就可以在浏览器中打开Performance工具:
下面的代码中,我们在build方法中计算1w次MD5的数值,以此观察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 按钮主动触发Performance进行CPU数据采集,然后我们就可以在真机设备上进行操作,一顿操作之后就可以在网页上看到记录的信息:
Performance记录的应用执行状况叫做CPU帧图,也被称为火焰图。它是用来记录代码执行情况的图,用来展示CPU的调用栈,表示了CPU的繁忙状况。
其中纵轴(y轴)表示调用栈,每一层都是一个函数,调用栈越深,执行时间越长;横轴(x轴)表示该函数在每个单位时间内被采样到的次数,一个函数在x轴长度越长,就表示它被采样的次数越多,执行时间越长。
针对这些耗时的操作,我们可以使用Isolate进行