17 個提高效能的 Flutter 最佳實踐

會煮咖啡的貓發表於2022-05-03

與其他混合平臺相比, Flutter 效能夠快嗎?答案是肯定的,但是出於這種考慮,讓我們來看看一些令人驚歎的效能和優化實踐。

原文

https://inficial.medium.com/f...

參考

正文

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,對效能的影響最小。

https://pub.dev/packages/nil

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 應用程式的效能。快樂的編碼!


© 貓哥

訂閱號

相關文章