與其他混合平臺相比, Flutter 效能夠快嗎?答案是肯定的,但是出於這種考慮,讓我們來看看一些令人驚歎的效能和優化實踐。
原文
https://inficial.medium.com/f...
參考
- https://api.flutter.dev/flutt...
- https://blog.codemagic.io/how...
- https://pub.dev/packages/nil
- https://medium.com/flutter-co...
- https://dart.dev/guides/langu...
- https://dart-lang.github.io/l...
- https://itnext.io/comparing-d...
- https://iisprey.medium.com/ge...
正文
1. 使用 Widgets 代替函式
不要這樣用它
Widget _buildFooterWidget() {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Text('This is the footer '),
);
}.
像這樣使用它
class FooterWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Text('This is the footer '),
);
}
}
如果一個函式可以做同樣的事情,Flutter 就不會有 StatelessWidget。
類似地,它主要是針對公共 widgets 的,這些 widgets 可以重複使用。私人函式只能使用一次並不重要ーー儘管意識到這種行為仍然是好的。
使用函式而不是類之間有一個重要的區別,那就是: 框架不知道函式,但是可以看到類。
考慮下面的“ widget”函式:
Widget functionWidget({ Widget child}) {
return Container(child: child);
}
這樣使用:
functionWidget(
child : functionWidget()
)
而且它是等價的:
class ClassWidget extends StatelessWidget {
final Widget child;
const ClassWidget({Key key, this.child}) : super(key: key); @override
Widget build(BuildContext context) {
return Container(
child: child,
);
}
}
這樣使用:
new ClassWidget(
child : new ClassWidget(),
)
在紙面上,兩者似乎做了同樣的事情: 建立 2 個容器,一個巢狀在另一箇中。但現實情況略有不同。
對於函式,生成的 widgets 樹如下所示:
Container
Container
對於類,widgets 樹是:
ClassWidget
Container
ClassWidget
Container
這一點很重要,因為它改變了框架在更新 widgets 時的行為。
為什麼這很重要
通過使用函式將 widgets 樹拆分為多個 widgets,您將自己暴露在 bug 之中,並錯過了一些效能優化。
不能保證使用函式會出現 bug,但是使用類可以保證不會遇到這些問題。
the issues:
下面是一些在 Dartpad 上的互動式例子,你可以自己跑步來更好地理解這些問題:
- https://dartpad.dev/1870e726d... 這個例子展示瞭如何通過分割你的應用程式的功能,你可能會不小心破壞諸如 AnimatedSwitcher 之類的東西
- https://dartpad.dev/a869b21a2...
這個例子展示了類如何允許更細粒度地重新構建 widgets 樹,從而提高效能 - https://dartpad.dev/06842ae9e...
這個例子展示了在使用 InheritedWidgets (比如主題或提供程式)時,如何通過使用函式使自己暴露在錯誤使用 BuildContext 和錯誤面前
總的來說,由於這些原因,在類上使用函式來重用 widgets 被認為是一種不好的做法。你可以,但它可能會在未來咬你。
- 避免重複重建所有 widgets
- 而且,新增 const 是個好主意。
- ListView 長列表的使用者專案範圍。
指定 itemExtent 比讓子滾動機制確定其範圍更有效,因為滾動機制可以利用子滾動機制的預先知識來節省工作,例如當滾動位置發生劇烈變化時。
- 避免在 animatedBuilder 中重建不必要的 widgets
不要這樣用它
body: AnimatedBuilder(
animation: _controller,
builder: (_, child) => Transform(
alignment: Alignment.center,
transform: Matrix4.identity()
..setEntry(3, 2, 0.001)
..rotateY(360 * _controller.value * (pi / 180.0)),
child: CounterWidget(
counter: counter,
),),
),
這將在出現動畫時重新構建 CounterWidget widgets。如果您將 log 放入 counterWidget 的 build 方法,那麼您可以看到每次出現動畫時它都會列印一個日誌。
像這樣使用它。
body: AnimatedBuilder(
animation: _controller,
child : CounterWidget(
counter: counter,
),
builder: (_, child) => Transform(
alignment: Alignment.center,
transform: Matrix4.identity()
..setEntry(3, 2, 0.001)
..rotateY(360 * _controller.value * (pi / 180.0)),
child: child),
),
我們使用 AnimatedBuilder 提供的子屬性,它允許我們快取 widgets 以在動畫中重用它們。我們這樣做是因為這個 widgets 不會改變,它唯一要做的就是旋轉,但是為此,我們有了 Transform widgets。
檢視更多資訊:
https://blog.codemagic.io/how...
2. 儘可能使用 const
class CustomWidget extends StatelessWidget {
const CustomWidget(); @override
Widget build(BuildContext context) {
...
}
}
- 在構建 widgets 時,或者使用 flutter widgets 時。這有助於 flutter 只重建應該更新的 widgets。當 setState 呼叫時,widgets 不會更改。它將阻止 widgets 重新構建。
- 您可以節省 CPU 週期,並將它們與 const 建構函式一起使用。
3. 用 nil 代替 const Container ()
// good
text != null ? Text(text) : const Container()
// Better
text != null ? Text(text) : const SizedBox()
// BEST
text != null ? Text(text) : nil
or
if (text != null) Text(text)
當您不想顯示任何內容時,在 widgets 樹中新增一個簡單的 widgets,對效能的影響最小。
4. 用 keys 提高 Flutter 效能
// FROM
return value
? const SizedBox()
: const Placeholder(),
// TO
return value
? const SizedBox(key: ValueKey('SizedBox'))
: const Placeholder(key: ValueKey('Placeholder')),
----------------------------------------------
// FROM
final inner = SizedBox();
return value ? SizedBox(child: inner) : inner,
// TO
final global = GlobalKey();
final inner = SizedBox(key: global);
return value ? SizedBox(child: inner) : inner,
通過使用鍵的顫動更好地識別 widgets,這提供了更好的效能。
5. 使用影像 ListView 時優化記憶體
ListView.builder(
...
addAutomaticKeepAlives: false (true by default)
addRepaintBoundaries: false (true by default)
);
ListView 無法殺死它的子節點,因為它們在螢幕上不可見。如果兒童有高解析度的影像,會消耗大量的記憶體。
做這些選項錯誤,可能導致使用更多的 GPU 和 CPU 工作,但它可以解決我們的記憶體問題,您將得到一個非常高效能的看法沒有明顯的問題。
Flutter 記憶優化系列
幾乎在 Flutter 的任何東西都是預設優化和增強的,正如你可能已經知道的那樣,感謝 Flutter 團隊在..
https://medium.com/flutter-co...
6. 遵循 Dart 風格
Identifiers:: 識別符號有三種風格
- UpperCamelCase 名稱將每個單詞的首字母大寫,包括第一個單詞。
- lowerCamelCase 每個單詞的第一個字母都大寫,除了第一個字母總是小寫,即使它是首字母縮略詞。
- lowercase*with_underscores 名稱只使用小寫字母,即使對於首字母縮寫也是如此,而使用 \_ 的單詞也是如此。
使用 UpperCamelCase 命名型別
類、列舉型別、 typedef 和型別引數應該大寫每個單詞(包括第一個單詞)的第一個字母,並且不使用分隔符。
good
class SliderMenu { ... }
class HttpRequest { ... }
typedef Predicate<T> = bool Function(T value);
const foo = Foo();
@foo
class C { ... }
使用以下命名庫、包、目錄和原始檔
Good
library peg_parser.source_scanner;
import 'file_system.dart';
import 'slider_menu.dart';
Bad
library pegparser.SourceScanner;
import 'file-system.dart';
import 'SliderMenu.dart';
要了解更多樣式,請檢視 Dart 樣式:
好的程式碼的一個驚人的重要部分是好的風格。一致的命名、排序和格式有助於程式碼..
https://dart.dev/guides/langu...
要了解更多規則,請檢視 Dart 聯結器:
https://dart-lang.github.io/l...
7. 只在需要的時候使用 MediaQuery/LayoutBuilder
8. 使用 async/await 代替 then 函式
9. 有效使用操作符
var car = van == null ? bus : audi; // Old pattern
var car = audi ?? bus; // New pattern
var car = van == null ? null : audi.bus; // Old pattern
var car = audi?.bus; // New pattern
(item as Car).name = 'Mustang'; // Old pattern
if (item is Car) item.name = 'Mustang'; // New pattern
10. 利用字串模板內插
// Inappropriate
var discountText = 'Hello, ' + name + '! You have won a brand new ' + brand.name + 'voucher! Please enter your email to redeem. The offer expires within ' + timeRemaining.toString() ' minutes.';
// Appropriate
var discountText = 'Hello, $name! You have won a brand new ${brand.name} voucher! Please enter your email to redeem. The offer expires within ${timeRemaining} minutes.';
11. 使用 for/while 代替 foreach/map
您可以在本文中檢查迴圈的比較
比較 Dart 的線圈ーー哪一個最快?
編寫 Flutter apps 的語言 Dart 有許多不同的迴圈,它們可以迴圈遍歷列表或執行一些..
https://itnext.io/comparing-d...
12. 精確顯示你的圖片和圖示
precacheImage(AssetImage(imagePath), context);For SVGs
you need flutter_svg package.precachePicture(
ExactAssetPicture(
SvgPicture.svgStringDecoderBuilder,iconPath),context,
);
13. 使用 SKSL Warmup
flutter run --profile --cache-sksl --purge-persistent-cache
flutter build apk --cache-sksl --purge-persistent-cache
如果一個應用程式在第一次執行時有簡潔的動畫,之後對於同樣的動畫變得流暢,那麼這很可能是由於著色器編譯的延遲。
14. 考慮使用 RepaintBoundary widgets
Flutter widgets 與渲染物件相關聯。渲染物件有一個名為 paint 的方法,用於執行繪製。但是,即使關聯的 widgets 例項不變,也可以呼叫 paint 方法。這是因為如果其中一個被標記為髒的話,Flutter 可能會對同一層中的其他渲染物件執行重新繪製。當渲染物件需要通過 RenderObject.markneedspaint 重新繪製時,它會告訴它最近的祖先重新繪製。祖先對其祖先執行相同的操作,可能直到根 RenderObject 為止。當一個渲染物件的 paint 方法被觸發時,它在同一層中的所有子渲染物件都將被重新繪製。
在某些情況下,當需要重新繪製渲染物件時,同一層中的其他渲染物件不需要重新繪製,因為它們呈現的內容保持不變。換句話說,如果我們只能重新繪製某些渲染物件會更好。使用 RepaintBoundary 對於限制 markneedspain 在渲染樹上的傳播和 paintChild 在渲染樹上的傳播非常有用。RepaintBoundary 可以將祖先呈現物件與後代呈現物件解耦。因此,只能重新繪製內容發生變化的子樹。使用 RepaintBoundary 可以顯著提高應用程式的效能,特別是如果不需要重新繪製的子樹需要大量的重新繪製工作時。
15. 如果可能的話,使用命名構造器
// 例如
Listview → Listview.builder
16. 適當處理資料
不必要的記憶體使用會在應用程式內悄悄地殺死資料,所以不要忘記處理你的資料
有些軟體包為它們的類提供自動釋放支援
如果你不知道什麼是 Flutter 掛鉤和如何使用它
用 Flutter 鉤子去除各種樣板程式碼
您不認為,現在是時候關閉 StatefulWidget 並使用 flutter hook 刪除樣板程式碼了嗎
https://iisprey.medium.com/ge...
將 cacheHeight 和 cacheWidth 設定為影像
您可以通過這種方式減少記憶體使用
17. 不要在 List Map 中使用引用
不要這樣使用
List a = [1,2,3,4];
List b;
b = a;
a.remove(1);
print(a); // [2,3,4]
print(b); // [2,3,4]
由於這個原因,每當您嘗試呼叫列表 a 的任何方法時,都會自動呼叫列表 b。
比如 a.remove (some) ; 也會從列表 b 中刪除該項;
像這樣使用它
List a = [1,2,3,4];
List b;
b = jsonDecode(jsonEncode(a));
a.remove(1);
print(a); // [2,3,4]
print(b); // [1,2,3,4]
end
我希望這給你一些見解,以改善您的 Flutter 應用程式的效能。快樂的編碼!
© 貓哥
- 微信 ducafecat
- 部落格 ducafecat.tech
- github
- bilibili