【Flutter】如何改进Flutter的性能

213 阅读3分钟

首先声明,Flutter本身的性能是没有问题的,关键在于如何使用。

我们编写的时候要避免这些错误,才能得到高效的Flutter应用。

1.不要把widgets拆倒方法里

当我们有一个巨大的布局页面,很多人通常的做法是分割成一个个多方法为widget。

下面的例子包含了header、center、footer widget

class MyHomePage extends StatelessWidget {
  Widget _buildHeaderWidget() {
    final size = 40.0;
    return Padding(
      padding: const EdgeInsets.all(8.0),
      child: CircleAvatar(
        backgroundColor: Colors.grey[700],
        child: FlutterLogo(
          size: size,
        ),
        radius: size,
      ),
    );
  }

  Widget _buildMainWidget(BuildContext context) {
    return Expanded(
      child: Container(
        color: Colors.grey[700],
        child: Center(
          child: Text(
            'Hello Flutter',
            style: Theme.of(context).textTheme.display1,
          ),
        ),
      ),
    );
  }

  Widget _buildFooterWidget() {
    return Padding(
      padding: const EdgeInsets.all(8.0),
      child: Text('This is the footer '),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Padding(
        padding: const EdgeInsets.all(15.0),
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            _buildHeaderWidget(),
            _buildMainWidget(context),
            _buildFooterWidget(),
          ],
        ),
      ),
    );
  }
}

事实上是我们刷新局部的时候会刷新整个widget,这样毫无疑问浪费了CPU。

正确的做法是把这些做成stateless Widget,如下

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Padding(
        padding: const EdgeInsets.all(15.0),
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            HeaderWidget(),
            MainWidget(),
            FooterWidget(),
          ],
        ),
      ),
    );
  }
}

class HeaderWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final size = 40.0;
    return Padding(
      padding: const EdgeInsets.all(8.0),
      child: CircleAvatar(
        backgroundColor: Colors.grey[700],
        child: FlutterLogo(
          size: size,
        ),
        radius: size,
      ),
    );
  }
}

class MainWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Expanded(
      child: Container(
        color: Colors.grey[700],
        child: Center(
          child: Text(
            'Hello Flutter',
            style: Theme.of(context).textTheme.display1,
          ),
        ),
      ),
    );
  }
}

class FooterWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(8.0),
      child: Text('This is the footer '),
    );
  }
}

Stateful/Stateless widgets 有特殊的缓存机制(基于key, widget type and attributes),只在必要的情况下刷新。除了这个,也帮助我们封装和重构我们的widgets。(分而治之)

同时,给我们的widgets添加const关键字也是一个好主意,后面详述。

2.避免重复的rebuilding所有的wigets

典型的错误就是当我们需要rebuild我们的StatefulWidget的时候用到了setState。

下面的例子是一个widget包括一个方块做中央,里面有个fav的按钮,每点击一次颜色就会变好,还有一个背景图片。

同时我们也添加了一些print的代码来看看他是如何工作的。

class _MyHomePageState extends State<MyHomePage> {
  Color _currentColor = Colors.grey;

  Random _random = new Random();

  void _onPressed() {
    int randomNumber = _random.nextInt(30);
    setState(() {
      _currentColor = Colors.primaries[randomNumber % Colors.primaries.length];
    });
  }

  @override
  Widget build(BuildContext context) {
    print('building `MyHomePage`');
    return Scaffold(
      floatingActionButton: FloatingActionButton(
        onPressed: _onPressed,
        child: Icon(Icons.colorize),
      ),
      body: Stack(
        children: [
          Positioned.fill(
            child: BackgroundWidget(),
          ),
          Center(
            child: Container(
              height: 150,
              width: 150,
              color: _currentColor,
            ),
          ),
        ],
      ),
    );
  }
}

class BackgroundWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    print('building `BackgroundWidget`');
    return Image.network(
      'https://cdn.pixabay.com/photo/2017/08/30/01/05/milky-way-2695569_960_720.jpg',
      fit: BoxFit.cover,
    );
  }
}

你将会看到两条日志

flutter: building `MyHomePage`
flutter: building `BackgroundWidget`

每次点击按钮,我们都会刷新整个屏幕,脚手架scaffold,背景图widget。

重建整个widget不是一个好主意,我们只需要更新真正需要更新的部分。

大家都知可以用像是flutter_bloc, mobx, provider 等等来解决这个问题,但很少有人知道实际上不需要任何扩展包也可以。Flutter 框架本身已经提供了这些。

我们用ValueNotifier重构上面的代码。

class _MyHomePageState extends State<MyHomePage> {
  final _colorNotifier = ValueNotifier<Color>(Colors.grey);
  Random _random = new Random();

  void _onPressed() {
    int randomNumber = _random.nextInt(30);
    _colorNotifier.value =
        Colors.primaries[randomNumber % Colors.primaries.length];
  }

  @override
  void dispose() {
    _colorNotifier.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    print('building `MyHomePage`');
    return Scaffold(
      floatingActionButton: FloatingActionButton(
        onPressed: _onPressed,
        child: Icon(Icons.colorize),
      ),
      body: Stack(
        children: [
          Positioned.fill(
            child: BackgroundWidget(),
          ),
          Center(
            child: ValueListenableBuilder(
              valueListenable: _colorNotifier,
              builder: (_, value, __) => Container(
                height: 150,
                width: 150,
                color: value,
              ),
            ),
          ),
        ],
      ),
    );
  }
}

现在你不会再看到那些print出来的东西了。

当我没想要分离业务逻辑和视图时,我们也可以用notifier这个东西来做。

下面的例子使用了ChangeNotifier

//------ ChangeNotifier class ----//
class MyColorNotifier extends ChangeNotifier {
  Color myColor = Colors.grey;
  Random _random = new Random();

  void changeColor() {
    int randomNumber = _random.nextInt(30);
    myColor = Colors.primaries[randomNumber % Colors.primaries.length];
    notifyListeners();
  }
}
//------ State class ----//

class _MyHomePageState extends State<MyHomePage> {
  final _colorNotifier = MyColorNotifier();

  void _onPressed() {
    _colorNotifier.changeColor();
  }

  @override
  void dispose() {
    _colorNotifier.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    print('building `MyHomePage`');
    return Scaffold(
      floatingActionButton: FloatingActionButton(
        onPressed: _onPressed,
        child: Icon(Icons.colorize),
      ),
      body: Stack(
        children: [
          Positioned.fill(
            child: BackgroundWidget(),
          ),
          Center(
            child: AnimatedBuilder(
              animation: _colorNotifier,
              builder: (_, __) => Container(
                height: 150,
                width: 150,
                color: _colorNotifier.myColor,
              ),
            ),
          ),
        ],
      ),
    );
  }
}

这样就避免了最后两个widgets不必要的刷新。

3.使用尽可能的 const关键字

4.在长列表ListView里使用itemExtent属性

5.在动画AnimatedBuilder里避免重建rebuild

6.避免在动画里使用透明度

3-6下次有时间再做补充

原文:链接