关于flutter:老孟Flutter如何提高Flutter应用程序的性能

25次阅读

共计 7852 个字符,预计需要花费 20 分钟才能阅读完成。

首先 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(width: 30,),
          Icon(
            iconData,
            color: iconColor,
          ),
          SizedBox(width: 30,),
          Expanded(child: Text('$title'),
          ),
          suffix,
          SizedBox(width: 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,
      width: 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,
          width: 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…

https://api.flutter.dev/flutt…

https://api.flutter.dev/flutt…

交换

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

增加微信或者公众号支付《330 个控件大全》和《Flutter 实战》PDF。

欢送退出 Flutter 交换群(微信:laomengit)、关注公众号【老孟 Flutter】:

正文完
 0