zoukankan      html  css  js  c++  java
  • 【老孟Flutter】如何提高Flutter应用程序的性能

    首先 Flutter 是一个非常高性能的框架,因此大多时候不需要开发者做出特殊的处理,只需要避免常见的性能问题即可获得高性能的应用程序。

    重建最小化原则

    在调用 setState() 方法重建组件时,一定要最小化重建组件,没有变化的组件不要重建,看下面的Demo,这是一个设置页面,

    import 'package:flutter/material.dart';
    
    class SettingDemo extends StatefulWidget {
      @override
      _SettingDemoState createState() => _SettingDemoState();
    }
    
    class _SettingDemoState extends State<SettingDemo> {
    
      Widget _item(
          {IconData iconData, Color iconColor, String title, Widget suffix}) {
        return Container(
          height: 45,
          child: Row(
            children: <Widget>[
              SizedBox(
                 30,
              ),
              Icon(
                iconData,
                color: iconColor,
              ),
              SizedBox(
                 30,
              ),
              Expanded(
                child: Text('$title'),
              ),
              suffix,
              SizedBox(
                 15,
              ),
            ],
          ),
        );
      }
    
      bool _switchValue = false;
    
      @override
      Widget build(BuildContext context) {
        return Column(
          children: <Widget>[
            _item(
              iconData: Icons.notifications,
              iconColor: Colors.blue,
              title: '是否允许4G网络下载',
              suffix: Switch(
                  value: _switchValue,
                  onChanged: (value) {
                    setState(() {
                      _switchValue = value;
                    });
                  }),
            ),
            Divider(),
            _item(
              iconData: Icons.notifications,
              iconColor: Colors.blue,
              title: '消息中心',
              suffix: Text(
                '12条',
                style: TextStyle(color: Colors.grey.withOpacity(.5)),
              ),
            ),
            Divider(),
            _item(
              iconData: Icons.thumb_up,
              iconColor: Colors.green,
              title: '我赞过的',
              suffix: Text(
                '121篇',
                style: TextStyle(color: Colors.grey.withOpacity(.5)),
              ),
            ),
            Divider(),
            _item(
              iconData: Icons.grade,
              iconColor: Colors.yellow,
              title: '收藏集',
              suffix: Text(
                '2个',
                style: TextStyle(color: Colors.grey.withOpacity(.5)),
              ),
            ),
            Divider(),
            _item(
              iconData: Icons.account_balance_wallet,
              iconColor: Colors.blue,
              title: '我的钱包',
              suffix: Text(
                '10万',
                style: TextStyle(color: Colors.grey.withOpacity(.5)),
              ),
            ),
          ],
        );
      }
    }
    

    注意看上图右边下半部分,点击切换开关的时候,所有的组件全部重建了,理想情况下,应该只是 Switch 组件进行切换,因此将 Switch 组件进行封装:

    class _SwitchWidget extends StatefulWidget {
      final bool value;
    
      const _SwitchWidget({Key key, this.value}) : super(key: key);
    
      @override
      __SwitchWidgetState createState() => __SwitchWidgetState();
    }
    
    class __SwitchWidgetState extends State<_SwitchWidget> {
      bool _value;
    
      @override
      void initState() {
        _value = widget.value;
        super.initState();
      }
    
      @override
      Widget build(BuildContext context) {
        return Switch(
          value: _value,
          onChanged: (value) {
            setState(() {
              _value = value;
            });
          },
        );
      }
    }
    

    使用:

    _item(
      iconData: Icons.notifications,
      iconColor: Colors.blue,
      title: '是否允许4G网络下载',
      suffix: _SwitchWidget(
        value: false,
      ),
    )
    

    此时看到重建的组件只有 _SwitchWidgetSwitch 组件,提高了性能。

    如果 Switch 组件的状态改变也会改变其它组件的状态,这是典型的组件间通信,这种情况下可以使用 InheritedWidget,但更建议使用状态管理框架(比如 Provider 等),而不是将其父组件改变为StatefulWidget。

    尽量不要将整个页面定义为 StatefulWidget 组件,因为一旦重建将重建此页面下所有的组件,尤其是 Switch 、Radio等组件状态的改变导致的重建,强烈建议对其进行封装。

    这里有一个误区,有些人认为,将组件拆分为方法可以减少重建,就比如上面的例子,将 _SwitchWidget 组件改变为方法,该方法返回 Switch 组件,这是错误的,此种方式并不能减少重建, 但是将一个组件拆分为多个小组件是可以减少重建的,就像上面的例子,将需要重建的 Switch 封装为一个单独的 StatefulWidget 组件,避免了其他不必要的重建。

    强烈建议:在组件前加上 const

    在组件前加上 const ,相当于对此组件进行了缓存,下面是未加 const 的代码:

    class ConstDemo extends StatefulWidget {
      @override
      _ConstDemoState createState() => _ConstDemoState();
    }
    
    class _ConstDemoState extends State<ConstDemo> {
      @override
      Widget build(BuildContext context) {
        return Center(
          child: Column(
            children: [
              Text('老孟'),
              RaisedButton(onPressed: (){
                setState(() {
    
                });
              })
            ],
          ),
        );
      }
    }
    

    Text('老孟') 组件加上 const

    const Text('老孟'),
    

    对比两次 Text 组件的重建情况,加上 const 后,未重建。

    避免更改组件树的结构和组件的类型

    有如下场景,有一个 Text 组件有可见和不可见两种状态,代码如下:

    bool _visible = true;
    
    @override
    Widget build(BuildContext context) {
      return Center(
        child: Column(
          children: [
            if(_visible)
              Text('可见'),
            Container(),
          ],
        ),
      );
    }
    

    可见时的组件树:

    不可见时的组件树:

    两种状态组件树结构发生变化,应该避免发生此种情况,优化如下:

    Center(
      child: Column(
        children: [
          Visibility(
            visible: _visible,
            child: Text('可见'),
          ),
          Container(),
        ],
      ),
    )
    

    此时不管是可见还是不可见状态,组件树都不会发生变化,如下:

    还有一种情况是根据不同的条件构建不同的组件,如下:

    bool _showButton = true;
    
    @override
    Widget build(BuildContext context) {
      return Center(
        child: Column(
          children: [
            _showButton ? RaisedButton(onPressed: null) : Text('不显示'),
            Container(),
          ],
        ),
      );
    }
    

    设置为 true 时的组件树结构:

    设置为 false 时的组件树结构:

    看到左侧子节点由 RaisedButton 变为了 Text

    上面的情况组件树发生了更改,不管是类型发生更改,还是深度发生更改,如果无法避免,那么就将变化的组件树封装为一个 StatefulWidget 组件,且设置 GlobalKey,如下:

    封装变化的部分:

    class ChildWidget extends StatefulWidget {
      const ChildWidget({Key key}) : super(key: key);
    
      @override
      _ChildWidgetState createState() => _ChildWidgetState();
    }
    
    class _ChildWidgetState extends State<ChildWidget> {
      bool _showButton = true;
    
      @override
      Widget build(BuildContext context) {
        return _showButton ? RaisedButton(onPressed: null) : Text('不显示');
      }
    }
    

    构建:

    class ConstDemo extends StatefulWidget {
      @override
      _ConstDemoState createState() => _ConstDemoState();
    }
    
    class _ConstDemoState extends State<ConstDemo> {
      @override
      Widget build(BuildContext context) {
        return Center(
          child: Column(
            children: [
              ChildWidget(key: GlobalKey(),),
              Container(),
            ],
          ),
        );
      }
    }
    

    虽然通过 GlobalKey 提高了上面案例的性能,但我们千万不要乱用 GlobalKey,因为管理 GlobalKey 的成本很高,所以其他需要使用 Key 的地方建议考虑使用 Key, ValueKey, ObjectKey, 和 UniqueKey

    关于 GlobalKey 的相关说明参考:https://api.flutter.dev/flutter/widgets/GlobalKey-class.html

    关于ListView 的优化

    ListView是我们最常用的组件之一,用于展示大量数据的列表。如果展示大量数据请使用 ListView.builder 或者 ListView.separated,千万不要直接使用如下方式:

    ListView(
      children: <Widget>[
        item,item1,item2,...
      ],
    )
    

    这种方式一次加载所有的组件,没有“懒加载”,消耗极大的性能。

    ListView 中 itemExtent 属性对动态滚动到性能提升非常大,比如,有2000条数据展示,点击按钮滚动到最后,代码如下:

    class ListViewDemo extends StatefulWidget {
      @override
      _ListViewDemoState createState() => _ListViewDemoState();
    }
    
    class _ListViewDemoState extends State<ListViewDemo> {
      ScrollController _controller;
    
      @override
      void initState() {
        super.initState();
        _controller = ScrollController();
      }
    
      @override
      Widget build(BuildContext context) {
        return Stack(
          children: [
            ListView.builder(
              controller: _controller,
              itemBuilder: (context, index) {
                return Container(
                  height: 80,
                  alignment: Alignment.center,
                  color: Colors.primaries[index % Colors.primaries.length],
                  child: Text('$index',style: TextStyle(color: Colors.white,fontSize: 20),),
                );
              },
              itemCount: 2000,
            ),
            Positioned(
                child: RaisedButton(
              child: Text('滚动到最后'),
              onPressed: () {
                _controller.jumpTo(_controller.position.maxScrollExtent);
              },
            ))
          ],
        );
      }
    }
    

    耗时在2秒左右,加上 itemExtent 属性,修改如下:

    ListView.builder(
      controller: _controller,
      itemBuilder: (context, index) {
        return Container(
          height: 80,
          alignment: Alignment.center,
          color: Colors.primaries[index % Colors.primaries.length],
          child: Text('$index',style: TextStyle(color: Colors.white,fontSize: 20),),
        );
      },
      itemExtent: 80,
      itemCount: 2000,
    )
    

    优化后瞬间跳转到底部。

    这是因为不设置 itemExtent 属性,将会由子组件自己决定大小,大量的计算导致UI堵塞。

    关于 AnimatedBuilder TweenAnimationBuilder 的优化

    这里说的是向AnimatedBuilder 、TweenAnimationBuilder 等一类的组件的问题,这些组件都有一个共同点,带有 builder 且其参数重有 child

    以 AnimatedBuilder 为例,如果 builder 中构建的树中包含与动画无关的组件,将这些无关的组件当作 child 传递到 builder 中比直接在 builder 中构建更加有效。

    比如下面的代码,直接在 builder 中构建子组件:

    AnimatedBuilder(
        animation: animation,
        builder: (BuildContext context, Widget child) {
          return Transform.rotate(
            angle: animation.value,
            child: FlutterLogo(size: 60,),
          );
        },
      )
    

    优化后的代码:

    AnimatedBuilder(
        animation: animation,
        builder: (BuildContext context, Widget child) {
          return Transform.rotate(
            angle: animation.value,
            child: child,
          );
        },
        child: FlutterLogo(size: 60,),
      )
    

    谨慎的使用一些组件

    部分组件一定要谨慎使用,因为这些组件包含一些昂贵的操作,比如 saveLayer() 方法。

    调用saveLayer()会分配一个屏幕外缓冲区。 将内容绘制到屏幕外缓冲区中可能会触发渲染目标切换,这在较早的GPU中特别慢。

    另外虽然下面这些组件比较消耗性能,但并不是禁止大家使用,而是谨慎使用,如果有替代方案,考虑使用替代方法。

    尤其注意,如果这些组件频繁重建(比如动画的过程),要重点优化。

    Clip 类组件

    Clip 类组件是常用的裁剪类组件,比如:ClipOval、ClipPath、ClipRRect、ClipRect、CustomClipper。这些组件中都有 clipBehavior 属性,不同的值性能是不同的,

    ///  * [hardEdge], which is the fastest clipping, but with lower fidelity.
    ///  * [antiAlias], which is a little slower than [hardEdge], but with smoothed edges.
    ///  * [antiAliasWithSaveLayer], which is much slower than [antiAlias], and should
    ///    rarely be used.
    

    越往下,速度越慢。

    一些简单的圆角组件的设置可以使用 Container 实现:

    Container(
          height: 200,
           200,
          decoration: BoxDecoration(
            image:  DecorationImage(
              image: NetworkImage(
                  'https://flutter.github.io/assets-for-api-docs/assets/widgets/owl-2.jpg'),
              fit: BoxFit.cover,
            ),
            border: Border.all(
              color: Colors.blue,
               2,
            ),
            borderRadius: BorderRadius.circular(12),
          ),
        )
    

    Opacity

    Opacity 组件的功能是使子组件透明。此类将其子级绘制到中间缓冲区中,然后将子级混合回到部分透明的场景中。

    对于除0.0和1.0之外的不透明度值,此类相对昂贵,因为它需要将子级绘制到中间缓冲区中。 对于值0.0,根本不绘制子级。 对于值1.0,将立即绘制没有中间缓冲区的子对象。

    如果仅仅是对单个 Image 或者 Color 增加透明度,直接使用比 Opacity 组件更快:

     Container(color: Color.fromRGBO(255, 0, 0, 0.5))
    

    比使用 Opacity 组件更快:

    Opacity(opacity: 0.5, child: Container(color: Colors.red))
    

    如果对组件的透明度进行动画操作,建议使用 AnimatedOpacity

    还有一些组件也要慎重使用,比如:

    • ShaderMask
    • ColorFilter
    • BackdropFilter

    文中如果有不完善或者不正确的地方欢迎提出意见,后面如果优化的补充将会在我的博客中进行补充,地址:

    http://laomengit.com/blog/20201227/improve_performance.html

    参考链接:

    https://flutter.dev/docs/perf/rendering/best-practices

    https://api.flutter.dev/flutter/widgets/Opacity-class.html#transparent-image

    https://api.flutter.dev/flutter/widgets/StatefulWidget-class.html#performance-considerations

    交流

    老孟Flutter博客(330个控件用法+实战入门系列文章):http://laomengit.com

    添加微信或者公众号领取 《330个控件大全》和 《Flutter 实战》PDF。

    欢迎加入Flutter交流群(微信:laomengit)、关注公众号【老孟Flutter】:

  • 相关阅读:
    P1486 [NOI2004]郁闷的出纳员
    poj2155 Matrix
    [USACO10MAR]伟大的奶牛聚集Great Cow Gat…
    分布式相关学习整理
    分布式学习之JTA
    linux相关命令整理
    每日笔记-redis的理解及相关应用
    利用redis实现分布式锁知识点总结及相关改进
    每日笔记-类加载机制及相关拓展
    多线程学习系列-概述
  • 原文地址:https://www.cnblogs.com/mengqd/p/14306082.html
Copyright © 2011-2022 走看看