- 原文地址: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、前端、後端、區塊鏈、產品、設計、人工智能等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃、官方微博、知乎專欄。