Flutter_29_Flutter性能监控和优化

在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进行


————————————————
版权声明:本文为CSDN博主「百家晓东」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/tianmaxingkong_/article/details/104385758

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值