- 原文地址:How fast is Flutter? I built a stopwatch app to find out.
- 原文做者:Andrea Bizzotto
- 译文出自:掘金翻译计划
- 本文永久连接:github.com/xitu/gold-m…
- 译者:ALVINYEH
- 校对者:swants、talisk
图片来源: Petar Petkovskihtml
这个周末,我花了点时间去用由谷歌新开发的 UI 框架 Flutter。前端
从理论上讲,它听起来很是棒!android
根据文档,高性能是预料之中的:ios
Flutter 旨在帮助开发者轻松地实现恒定的 60 fps。git
可是 CPU 利用率如何?github
太长了读不下去,直接看评论:不如原生好。你必须正确地作到:编程
setState()
方法,请确保尽量少地从新绘制用户界面。我用 Flutter 框架开发了一个简单的秒表应用程序,并分析了 CPU 和内存的使用状况。后端
图左:iOS 秒表应用。 图右:用 Flutter 的版本。很漂亮吧?bash
主界面是这样创建的:
class TimerPage extends StatefulWidget {
TimerPage({Key key}) : super(key: key);
TimerPageState createState() => new TimerPageState();
}
class TimerPageState extends State<TimerPage> {
Stopwatch stopwatch = new Stopwatch();
void leftButtonPressed() {
setState(() {
if (stopwatch.isRunning) {
print("${stopwatch.elapsedMilliseconds}");
} else {
stopwatch.reset();
}
});
}
void rightButtonPressed() {
setState(() {
if (stopwatch.isRunning) {
stopwatch.stop();
} else {
stopwatch.start();
}
});
}
Widget buildFloatingButton(String text, VoidCallback callback) {
TextStyle roundTextStyle = const TextStyle(fontSize: 16.0, color: Colors.white);
return new FloatingActionButton(
child: new Text(text, style: roundTextStyle),
onPressed: callback);
}
@override
Widget build(BuildContext context) {
return new Column(
children: <Widget>[
new Container(height: 200.0,
child: new Center(
child: new TimerText(stopwatch: stopwatch),
)),
new Row(mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
buildFloatingButton(stopwatch.isRunning ? "lap" : "reset", leftButtonPressed),
buildFloatingButton(stopwatch.isRunning ? "stop" : "start", rightButtonPressed),
]),
],
);
}
}
复制代码
这是如何运做的呢?
setState()
会被调用,而后触发 build()
方法。build()
方法的一部分, 一个新的 TimerText
会被建立。TimerText
类看起来是这样的:
class TimerText extends StatefulWidget {
TimerText({this.stopwatch});
final Stopwatch stopwatch;
TimerTextState createState() => new TimerTextState(stopwatch: stopwatch);
}
class TimerTextState extends State<TimerText> {
Timer timer;
final Stopwatch stopwatch;
TimerTextState({this.stopwatch}) {
timer = new Timer.periodic(new Duration(milliseconds: 30), callback);
}
void callback(Timer timer) {
if (stopwatch.isRunning) {
setState(() {
});
}
}
@override
Widget build(BuildContext context) {
final TextStyle timerTextStyle = const TextStyle(fontSize: 60.0, fontFamily: "Open Sans");
String formattedTime = TimerTextFormatter.format(stopwatch.elapsedMilliseconds);
return new Text(formattedTime, style: timerTextStyle);
}
}
复制代码
一些注意事项:
TimerTextState
对象所建立。每次触发回调后,若是秒表在运行,就会调用 setState()
方法。build()
方法,并在更新的时候绘制一个新的 Text
对象。当我一开始开发这个 App 时,我管理了 TimerPage
类中对所有状态以及 UI 界面,其中包括了秒表和定时器。
这就意味着每次触发定时器的回调时,会从新构建整个 UI 界面。这是没必要要且低效的:只有包含了过去时间的 Text
对象须要从新绘制 —— 特别是当每 30 毫秒计时器触发一次时。
若是咱们考虑到未优化和已优化的部件树层次结构,这一点就变得更显而易见了:
建立一个独立的的 TimerText
类来封装定时器的逻辑,能够下降 CPU 负担。
换句话说:
setState()
方法,确保尽量少地从新绘制 UI 用户界面。Flutter 官方文档指出该平台对快速分配进行了优化:
Flutter 框架使用了一种功能式流程,这种流程很大程度上取决于内存分配器是否有效地处理了小型,短时间的分配工做。
也许重建一棵部件树不能算做“小型,短时间的分配”。实际上,个人代码优化了致使较低的 CPU 和内存使用率的问题(见下文)。
自从这篇文章发表以来,一些谷歌工程师注意到了这一点,并作出了进一步的优化。
更新后的代码经过将 TimerText
分为了两个 MinutesAndSeconds
和 Hundredths
控件,进一步减小了用户界面的重绘:
进一步的 UI 界面优化(来源:谷歌)。
它们将本身注册为定时器回调的监听器,而且只有状态发生改变时才会从新绘制。这进一步优化了性能,由于如今每 30 毫秒只有 Hundredths
控件会渲染。
我在发布模式下运行了这个应用程序(flutter run --release
):
我在 Xcode 中监控了三分钟的 CPU 和内存使用状况,并测试了三种不一样模式下的性能表现。
在最后一个测试中,CPU 使用状况图密切地追踪了 GPU 线程,而 UI 线程保持地至关稳定。
注意:在低速模式下以相同的基准运行,CPU 的使用率超过了 50%。随着时间的推移,内存使用量也在不断增加。
这可能意味着内存在开发模式下没有被释放。
关键要点:确保你的应用处于发布模式。
请注意,当 CPU 使用率超过 20% 时,Xcode 会报告出一个很是高的电力消耗警告。
我在不断思考这些结果。每秒触发 30 次而且从新渲染一个文本标签的定时器不该该占用 25 %的双核 1.4GHz 的 CPU。
Flutter 应用中的控件树是由声明式范型所构建的,而不是在 iOS 和安卓上的命令式编程模型。
可是,命令模式下性能是否更加好呢?
为了找到答案,我在 iOS 上开发了相同的秒表应用。
这是用 Swift 代码设置了一个定时器,而且每 30 毫秒更新一次文本标签:
startDate = Date()
Timer.scheduledTimer(withTimeInterval: 0.03, repeats: true) { timer in
let elapsed = Date().timeIntervalSince(self.startDate)
let hundreds = Int((elapsed - trunc(elapsed)) * 100.0)
let seconds = Int(trunc(elapsed)) % 60
let minutes = seconds / 60
let hundredsStr = String(format: "%02d", hundreds)
let secondsStr = String(format: "%02d", seconds)
let minutesStr = String(format: "%02d", minutes)
self.timerLabel.text = "\(minutesStr):\(secondsStr).\(hundredsStr)"
}
复制代码
为了完整性,这是我在 Dart 中使用的时间格式代码(优化方案 1):
class TimerTextFormatter {
static String format(int milliseconds) {
int hundreds = (milliseconds / 10).truncate();
int seconds = (hundreds / 100).truncate();
int minutes = (seconds / 60).truncate();
String minutesStr = (minutes % 60).toString().padLeft(2, '0');
String secondsStr = (seconds % 60).toString().padLeft(2, '0');
String hundredsStr = (hundreds % 100).toString().padLeft(2, '0');
return "$minutesStr:$secondsStr.$hundredsStr";
}
}
复制代码
最后结果如何?
Flutter. CPU:25%,内存:22 MB
iOS. CPU:7%,内存:8 MB
Flutter 实现方式在 CPU 的使用状况超过了 3 倍以上,内存上也一样是 3 倍之多。
当定时器中止运行时,CPU 的使用率回到了 1%。这就证明了所有 CPU 的工做都用于处理定时器的回调和从新绘制 UI 界面。
这并不足以让人惊讶。
Text
控件。UILabel
的文本。“嘿!” —— 我听到你说的。“可是时间格式的代码是不一样的!你怎么知道 CPU 使用率的差别不是由于这个?”
那么,咱们不进行格式去修改这两个例子:
Swift:
startDate = Date()
Timer.scheduledTimer(withTimeInterval: 0.03, repeats: true) { timer in
let elapsed = Date().timeIntervalSince(self.startDate)
self.timerLabel.text = "\(elapsed)"
}
复制代码
Dart:
class TimerTextFormatter {
static String format(int milliseconds) {
return "$milliseconds";
}
}
复制代码
最新结果:
Flutter. CPU:15%,内存:22 MB
iOS. CPU:8%,内存:8 MB
Flutter 的实现仍然是 CPU-intensive 的两倍。此外,它彷佛在多线程(GPU,I/O 工做)上作了至关多的事情。但在 iOS 上,只有一个线程是处于活动状态的。
我用一个具体的案例来对比了 Flutter/Dart 和 iOS/Swift 的性能表现。
数字是不会说谎的。当涉及到频繁的 UI 界面更新时候,鱼和熊掌不可兼得。 🎂
Flutter 框架让开发者用一样的代码库为 iOS 和安卓开发应用程序,像热加载等功能进一步提升了开发效率。但 Flutter 仍然处于初期阶段。我但愿谷歌和社区能够改进运行时配置文件,更好地将好处带给终端用户。
至于你的应用程序,请务必考虑对代码进行微调,以减小用户界面的重绘。这份努力是值得。
我将这个项目的全部代码托管在这个 GitHub 仓库,你能够本身来运行一下。
不用客气!😊
这个样品项目是我第一次使用 Flutter 框架的实验。若是你知道如何编写更优雅的代码,我很乐意收到你的评论。
关于我: 我是一个自由职业的 iOS 开发者,同时兼顾在职工做,开源,写小项目和博客。
这是个人推特:@biz84。GiHub 主页:GitHub。欢迎一切的反馈,推文,有趣的资讯!想知道我最喜欢什么?许多的掌声 👏👏👏。噢,还有香蕉和面包。
掘金翻译计划 是一个翻译优质互联网技术文章的社区,文章来源为 掘金 上的英文分享文章。内容覆盖 Android、iOS、前端、后端、区块链、产品、设计、人工智能等领域,想要查看更多优质译文请持续关注 掘金翻译计划、官方微博、知乎专栏。