Flutter 性能优化实践———UI方向

1,274 阅读3分钟

1、前言

本文在阅读完多篇性能优化文章后结合自身项目做了一些总结归纳。说白了就是局部更新、区域自治和前端的组件化、模块化。具体可以参考官方文档或是观看这个视频:Flutter的性能测试和理论

2、重建最小化(控制刷新范围)

我们使用setState方法就可以轻松刷新页面,但是要尽力控制刷新范围。我举一个例子:

在订单页面会有一个支付倒计时,需要每隔一秒刷新一下倒计时显示的数字。

如果倒计时逻辑处理放在页面层级,那么每当setState时都是整个页面的刷新。而这整页刷新显然是不必要的。而它并不会让你感知到卡顿,所以也不易发现。

解决方法就是将这个倒计时的按钮单独封装到一个StatefulWidget,在这个StatefulWidget中使用setState刷新,控制刷新范围。

同样的,你也可以使用provider等状态管理框架来实现局部刷新。精准控制你的刷新范围,千万不要任何啥时候setState刷新整个页面层级。

3、控制刷新次数

比起控制刷新范围,控制刷新次数(避免无效刷新)甚至更加重要。 那么我们的做法就是监听TextField的文字输入,每次输入时判断是否满足条件,更新按钮是否可点击的状态。

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

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 时的组件树结构:

上面的情况组件树发生了更改,不管是类型发生更改,还是深度发生更改,如果无法避免,那么就将变化的组件树封装为一个 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: ValueKey(),),
          Container(),
        ],
      ),
    );
  }
}

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

文档参考