【问题标题】:Flutter: How can i improve that app performance?Flutter:我怎样才能提高应用程序的性能?
【发布时间】:2020-05-27 11:45:24
【问题描述】:

我想知道为什么 Flutter 代码在某些设备中低于 60 fps(在这种情况下,Redmi Go 是分析模式,在 UI 图中显示红色条)。有什么办法可以优化它吗?我昨天开始学习 Flutter,所以我想要一些技巧来帮助我理解有状态的小部件和小部件渲染层次结构。

提前致谢。

文件ma​​in.dart

import 'package:project/ui/home.dart';
import 'package:flutter/material.dart';

void main() => runApp(new MaterialApp(
      home: BillSplitter(),
    ));

文件home.dart

import 'package:flutter/material.dart';

class BillSplitter extends StatefulWidget {
  @override
  _BillSplitterState createState() => _BillSplitterState();
}

class _BillSplitterState extends State<BillSplitter> {
  int _tipPercentage = 0;
  int _personCounter = 1;
  double _billAmount = 0.0;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(
          "APP Teste 1.0",
          style: TextStyle(fontWeight: FontWeight.bold),
        ),
        centerTitle: true,
        backgroundColor: Colors.purple.withOpacity(0.5),
      ),
      body: Container(
        margin: EdgeInsets.only(top: MediaQuery.of(context).size.height * 0.1),
        alignment: Alignment.center,
        color: Colors.white,
        child: ListView(
          scrollDirection: Axis.vertical,
          padding: EdgeInsets.all(20.5),
          children: <Widget>[
            Container(
              width: 150,
              height: 150,
              decoration: BoxDecoration(
                  color: Colors.purple.withOpacity(0.1),
                  borderRadius: BorderRadius.circular(12.0)),
              child: Center(
                child: Column(
                  mainAxisAlignment: MainAxisAlignment.center,
                  children: <Widget>[
                    Text(
                      "Total por pessoa",
                      style: TextStyle(
                          color: Colors.purple,
                          fontWeight: FontWeight.normal,
                          fontSize: 17.0),
                    ),
                    Padding(
                      padding: const EdgeInsets.all(10.0),
                      child: Text(
                        "R\$ ${_calculateTotalPerPerson(_billAmount, _personCounter, _tipPercentage)}",
                        style: TextStyle(
                            color: Colors.purple,
                            fontWeight: FontWeight.bold,
                            fontSize: 35.0),
                      ),
                    )
                  ],
                ),
              ),
            ),
            Container(
              margin: EdgeInsets.only(top: 20.0),
              padding: EdgeInsets.all(12.0),
              decoration: BoxDecoration(
                  color: Colors.transparent,
                  border: Border.all(
                      color: Colors.blueGrey.shade100,
                      style: BorderStyle.solid),
                  borderRadius: BorderRadius.circular(12.0)),
              child: Column(
                children: <Widget>[
                  TextField(
                    keyboardType:
                        TextInputType.numberWithOptions(decimal: true),
                    style: TextStyle(color: Colors.purple),
                    decoration: InputDecoration(
                        prefixText: "Total da Conta: R\$ ",
                        prefixIcon: Icon(Icons.attach_money)),
                    onChanged: (String value) {
                      try {
                        _billAmount = double.parse(value);
                      } catch (e) {
                        _billAmount = 0.0;
                      }
                    },
                  ),
                  Row(
                    mainAxisAlignment: MainAxisAlignment.spaceBetween,
                    children: <Widget>[
                      Text(
                        "Dividir",
                        style: TextStyle(color: Colors.grey.shade700),
                      ),
                      Row(
                        children: <Widget>[
                          InkWell(
                            onTap: () {
                              setState(() {
                                if (_personCounter > 1) {
                                  _personCounter--;
                                }
                              });
                            },
                            child: Container(
                              width: 40.0,
                              height: 40.0,
                              margin: EdgeInsets.all(10.0),
                              decoration: BoxDecoration(
                                  borderRadius: BorderRadius.circular(7.0),
                                  color: Colors.purpleAccent.withOpacity(0.1)),
                              child: Center(
                                  child: Text(
                                "-",
                                style: TextStyle(
                                    color: Colors.purple,
                                    fontWeight: FontWeight.bold,
                                    fontSize: 17.0),
                              )),
                            ),
                          ),
                          Text(
                            "$_personCounter",
                            style: TextStyle(
                                color: Colors.purple,
                                fontWeight: FontWeight.bold,
                                fontSize: 17.0),
                          ),
                          InkWell(
                            onTap: () {
                              setState(() {
                                _personCounter++;
                              });
                            },
                            child: Container(
                              width: 40.0,
                              height: 40.0,
                              margin: EdgeInsets.all(10.0),
                              decoration: BoxDecoration(
                                  borderRadius: BorderRadius.circular(7.0),
                                  color: Colors.purpleAccent.withOpacity(0.1)),
                              child: Center(
                                  child: Text(
                                "+",
                                style: TextStyle(
                                    color: Colors.purple,
                                    fontWeight: FontWeight.bold,
                                    fontSize: 17.0),
                              )),
                            ),
                          ),
                        ],
                      ),
                    ],
                  ),
                  Row(
                    mainAxisAlignment: MainAxisAlignment.spaceBetween,
                    children: <Widget>[
                      Text(
                        "Gorjeta",
                        style: TextStyle(color: Colors.grey.shade700),
                      ),
                      Padding(
                        padding: const EdgeInsets.all(18.0),
                        child: Text(
                          "R\$ ${(_calculateTotalTip(_billAmount, _personCounter, _tipPercentage)).toStringAsFixed(2)}",
                          style: TextStyle(
                              color: Colors.purple,
                              fontWeight: FontWeight.bold,
                              fontSize: 17.0),
                        ),
                      )
                    ],
                  ),
                  Column(
                    children: <Widget>[
                      Text(
                        "$_tipPercentage%",
                        style: TextStyle(
                            color: Colors.purple,
                            fontSize: 17.0,
                            fontWeight: FontWeight.bold),
                      ),
                      Slider(
                          activeColor: Colors.purple,
                          inactiveColor: Colors.grey,
                          min: 0,
                          max: 100,
                          divisions: 10,
                          value: _tipPercentage.toDouble(),
                          onChanged: (double value) {
                            setState(() {
                              _tipPercentage = value.round();
                            });
                          })
                    ],
                  )
                ],
              ),
            )
          ],
        ),
      ),
    );
  }

  _calculateTotalPerPerson(double billAmount, int splitBy, int tipPercentage) {
    double totalPerPerson =
        (billAmount + _calculateTotalTip(billAmount, splitBy, tipPercentage)) /
            splitBy;

    return totalPerPerson.toStringAsFixed(2);
  }

  _calculateTotalTip(double billAmount, int splitBy, int tipPercentage) {
    double totalTip = 0.0;

    if (billAmount < 0 || billAmount.toString().isEmpty || billAmount == null) {
    } else {
      totalTip = (billAmount * tipPercentage) / 100;
    }

    return totalTip;
  }
}

【问题讨论】:

  • 您需要在发布模式下运行应用程序才能看到真正的影响。执行flutter run --release 或通过flutter build apk 构建发布apk(这将产生flat arm64 和32 包含的应用程序)[flutter.dev/docs/deployment/android].
  • 如何在发布模式下调试/显示帧率?
  • 你可以使用 Dart DevTools。
  • DevTools 是否在发布模式下工作?

标签: android performance flutter mobile dart


【解决方案1】:

您正在使用单个大型构建函数,您需要将其拆分为不同的小部件,因为当数据状态发生变化并调用setState()时,所有后代小部件都会重新构建,因此请避免使用非常庞大的嵌套构建函数将非常昂贵。

应用程序的性能也会受到调试的影响,生成发布应用程序将更有帮助。

运行此命令来构建您的应用程序 flutter build apk.

您也可以查看Build and release an Android appBuild and release an iOS app

您可以使用 Flutter 分析工具来确定应用程序中的性能并确定性能问题,您可以在以下有关 Flutter 性能分析的链接中找到更多有用的详细信息。

Flutter performance profiling

还可以查看此链接以获取有关性能最佳实践的更多信息和详细信息。

Performance best practices

更新

根据Performance best practices

避免使用大型 build() 函数的过大单个小部件。分裂 它们基于封装分为不同的小部件,但也取决于如何 他们改变了:当 setState() 被一个 State 调用时,所有的后代 小部件将重建。因此,将 setState() 调用本地化为 UI 实际需要更改的子树的一部分。避免打电话 setState() 在树的高处,如果变化被包含在一个小的范围内 树的一部分。

并根据docs

调用 setState 通知框架内部状态 此对象的更改方式可能会影响用户界面 在这个子树中,这会导致框架为 这个 State 对象。

因此,最好将代码划分为小部件,以避免在长时间构建函数中出现大型嵌套小部件,我建议阅读State management,它对于管理应用程序周围的数据非常有用,并提高了可读性和可维护性您的代码,最终将带来更好的性能。

这里我提供了一个关于使用此问题中提供的代码进行小部件解耦的示例,您需要阅读以下代码中的 cmets 以了解我如何划分应用程序小部件,并且我使用了颤振性能和每秒帧数在调试模式下不低于 30,而您提供的代码下降到每秒 15 帧以下。

您可以比较两个代码并查看性能差异,并毫不犹豫地向我询问代码以进行澄清。

文件home.dart

import 'package:flutter/material.dart';

/*
  I created this class for a better data management around the application
  but this design is not recommended
 */
class Bill {
  static int _tipPercentage = 0;
  static int _personCounter = 1;
  static double _billAmount = 0.0;

  static _calculateTotalPerPerson(
      double billAmount, int splitBy, int tipPercentage) {
    double totalPerPerson =
        (billAmount + _calculateTotalTip(billAmount, splitBy, tipPercentage)) /
            splitBy;

    return totalPerPerson.toStringAsFixed(2);
  }

  static _calculateTotalTip(double billAmount, int splitBy, int tipPercentage) {
    double totalTip = 0.0;

    if (billAmount < 0 || billAmount.toString().isEmpty || billAmount == null) {
    } else {
      totalTip = (billAmount * tipPercentage) / 100;
    }

    return totalTip;
  }
}

// I converted the main widget to a stateless widget
// and for a better data management I highly recommend searching about
// state management
class BillSplitter extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(
          "APP Teste 1.0",
          style: TextStyle(fontWeight: FontWeight.bold),
        ),
        centerTitle: true,
        backgroundColor: Colors.purple.withOpacity(0.5),
      ),
      body: Container(
        margin: EdgeInsets.only(top: MediaQuery.of(context).size.height * 0.1),
        alignment: Alignment.center,
        color: Colors.white,
        /*
        Inside this ListView a lot of children widgets which can be converted
        to smaller widgets in different classes based on the state of the widget
        either it's stateless or stateful
         */
        child: ListView(
          scrollDirection: Axis.vertical,
          padding: EdgeInsets.all(20.5),
          children: <Widget>[
            CurrentBillContainer(), //Check this widget class down below
            BillCalculator(),
          ],
        ),
      ),
    );
  }
}

/*
  This container is for the upper pink box which holds the bill value
  and viewed in a different widget with a different build function
  which will enhance the build() function time
 */
class CurrentBillContainer extends StatefulWidget {
  @override
  _CurrentBillContainerState createState() => _CurrentBillContainerState();
}

class _CurrentBillContainerState extends State<CurrentBillContainer> {
  @override
  Widget build(BuildContext context) {
    return Container(
      width: 150,
      height: 150,
      decoration: BoxDecoration(
          color: Colors.purple.withOpacity(0.1),
          borderRadius: BorderRadius.circular(12.0)),
      child: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              "Total por pessoa",
              style: TextStyle(
                  color: Colors.purple,
                  fontWeight: FontWeight.normal,
                  fontSize: 17.0),
            ),
            Padding(
              padding: const EdgeInsets.all(10.0),
              child: Text(
                "R\$ ${Bill._calculateTotalPerPerson(Bill._billAmount, Bill._personCounter, Bill._tipPercentage)}",
                style: TextStyle(
                    color: Colors.purple,
                    fontWeight: FontWeight.bold,
                    fontSize: 35.0),
              ),
            )
          ],
        ),
      ),
    );
  }
}

class BillCalculator extends StatefulWidget {
  @override
  _BillCalculatorState createState() => _BillCalculatorState();
}

class _BillCalculatorState extends State<BillCalculator> {
  @override
  Widget build(BuildContext context) {
    return Container(
      margin: EdgeInsets.only(top: 20.0),
      padding: EdgeInsets.all(12.0),
      decoration: BoxDecoration(
          color: Colors.transparent,
          border: Border.all(
              color: Colors.blueGrey.shade100, style: BorderStyle.solid),
          borderRadius: BorderRadius.circular(12.0)),
      child: Column(
        children: <Widget>[
          TotalBillTextField(),
          DividirRow(),
          Row(
            mainAxisAlignment: MainAxisAlignment.spaceBetween,
            children: <Widget>[
              Text(
                "Gorjeta",
                style: TextStyle(color: Colors.grey.shade700),
              ),
              Padding(
                padding: const EdgeInsets.all(18.0),
                child: Text(
                  "R\$ ${(Bill._calculateTotalTip(Bill._billAmount, Bill._personCounter, Bill._tipPercentage)).toStringAsFixed(2)}",
                  style: TextStyle(
                      color: Colors.purple,
                      fontWeight: FontWeight.bold,
                      fontSize: 17.0),
                ),
              )
            ],
          ),
          Column(
            children: <Widget>[
              Text(
                "${Bill._tipPercentage}%",
                style: TextStyle(
                    color: Colors.purple,
                    fontSize: 17.0,
                    fontWeight: FontWeight.bold),
              ),
              Slider(
                  activeColor: Colors.purple,
                  inactiveColor: Colors.grey,
                  min: 0,
                  max: 100,
                  divisions: 10,
                  value: Bill._tipPercentage.toDouble(),
                  onChanged: (double value) {
                    setState(() {
                      Bill._tipPercentage = value.round();
                    });
                  })
            ],
          )
        ],
      ),
    );
  }
}

/*
  Take this TextField as an example you can create a stateless widget for it
  and place it inside the column of BillCalculator class
  and you can apply the same concept all over the application,
  and divide widgets to small classes and inside sub folders to reduce the size
  of the build function and optimize the performance and make it easier to
  maintain and add a new features in your application
 */
class TotalBillTextField extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return TextField(
      keyboardType: TextInputType.numberWithOptions(decimal: true),
      style: TextStyle(color: Colors.purple),
      decoration: InputDecoration(
          prefixText: "Total da Conta: R\$ ",
          prefixIcon: Icon(Icons.attach_money)),
      onChanged: (String value) {
        try {
          Bill._billAmount = double.parse(value);
        } catch (e) {
          Bill._billAmount = 0.0;
        }
      },
    );
  }
}

/*
  This row has to be a Stateful widget because you are using
  setState() function, you can apply the same method to all of the widgets
 */
class DividirRow extends StatefulWidget {
  @override
  _DividirRowState createState() => _DividirRowState();
}

class _DividirRowState extends State<DividirRow> {
  @override
  Widget build(BuildContext context) {
    return Row(
      mainAxisAlignment: MainAxisAlignment.spaceBetween,
      children: <Widget>[
        Text(
          "Dividir",
          style: TextStyle(color: Colors.grey.shade700),
        ),
        Row(
          children: <Widget>[
            InkWell(
              onTap: () {
                setState(() {
                  if (Bill._personCounter > 1) {
                    Bill._personCounter--;
                  }
                });
              },
              child: Container(
                width: 40.0,
                height: 40.0,
                margin: EdgeInsets.all(10.0),
                decoration: BoxDecoration(
                    borderRadius: BorderRadius.circular(7.0),
                    color: Colors.purpleAccent.withOpacity(0.1)),
                child: Center(
                    child: Text(
                      "-",
                      style: TextStyle(
                          color: Colors.purple,
                          fontWeight: FontWeight.bold,
                          fontSize: 17.0),
                    )),
              ),
            ),
            Text(
              "${Bill._personCounter}",
              style: TextStyle(
                  color: Colors.purple,
                  fontWeight: FontWeight.bold,
                  fontSize: 17.0),
            ),
            InkWell(
              onTap: () {
                setState(() {
                  Bill._personCounter++;
                });
              },
              child: Container(
                width: 40.0,
                height: 40.0,
                margin: EdgeInsets.all(10.0),
                decoration: BoxDecoration(
                    borderRadius: BorderRadius.circular(7.0),
                    color: Colors.purpleAccent.withOpacity(0.1)),
                child: Center(
                    child: Text(
                      "+",
                      style: TextStyle(
                          color: Colors.purple,
                          fontWeight: FontWeight.bold,
                          fontSize: 17.0),
                    )),
              ),
            ),
          ],
        ),
      ],
    );
  }
}

【讨论】:

  • 如果您检查代码,您会发现 setState() 'steps' 没有很多后代小部件。另外,您能否提供在我的情况下“小部件解耦”的示例?
  • 我所说的后代小部件是构建函数中的小部件,您在问题中提供的代码在单个有状态小部件中包含太多嵌套小部件,这肯定会影响应用程序的性能,并且会使你真的很难维护你的代码。我将根据您提供的代码用一个小部件解耦的示例更新我的答案,并提供一些关于 setState() 的解释。
  • 我测试了你的代码方法,应用程序没有按预期工作。
  • 您使CurrentBillContainer 有状态,但该小部件在数据更改时不会更新,仅在TotalBillTextFieldchanges 时更新。
  • 对不起那个错误,我真的不知道应用程序是如何工作的,我唯一的重点是优化性能,正如你在问题中提到的那样,也许我忘了更新变量或者我没有真的很确定,所以通过修复我的代码,它可能会按预期工作并获得更好的性能。对于状态管理,我强烈推荐阅读Provider package,它很容易实现和理解,而且你会在网上找到很多关于它的资源。
猜你喜欢
  • 1970-01-01
  • 2021-04-16
  • 2020-05-03
  • 1970-01-01
  • 2016-04-06
  • 1970-01-01
  • 2014-09-07
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多