乐趣区

关于前端:将Flutter推向极限每个开发人员都应该知道的性能提示

有没有感觉 Flutter 是较量中的乌龟?别放心!只有咱们有几招,咱们就能把乌龟变成涡轮增压的兔子。筹备好放大了吗?让咱们深刻理解一些 Flutter 性能技巧!

Flutter 应用程序在默认状况下是高性能的,因而您只须要防止常见的陷阱即可取得杰出的性能。如何设计和实现利用的 UI 会对利用的运行效率产生很大影响。

这些最佳实际倡议将帮忙您编写最高性能的 Flutter 应用程序。

让咱们开始浏览吧!!

1. 应用 Clean Architecture(洁净架构)

Clean Architecture 是一种软件设计模式,强调关注点拆散和独立测试。此模式激励将利用程序逻辑拆散到不同的层中,每一层负责一组特定的工作。Clean Architecture 非常适合大型应用程序,因为它提供了清晰的关注点拆散,并使测试更容易。

您能够查看此软件包 – clean_architecture_scaffold

以下是 Flutter 中的一个 Clean Architecture 实现示例:

lib/
  data/
    models/
      user_model.dart
    repositories/
      user_repository.dart
  domain/
    entities/
      user.dart
    repositories/
      user_repository_interface.dart
    usecases/
      get_users.dart
  presentation/
    pages/
      users_page.dart
    widgets/
      user_item.dart
    main.dart

2. 应用良好的状态治理

状态治理在 Flutter 利用性能中起着至关重要的作用。依据利用的复杂性抉择正确的状态治理办法。对于中小型应用程序,内置的 setState 办法可能就足够了。然而,对于更大更简单的应用程序,能够思考应用状态治理库,如 bloc 或 riverpod。

// Bad Approach
setState(() {
  // Updating a large data structure unnecessarily
  myList.add(newItem);
});

// Better Approach
final myListBloc = BlocProvider.of<MyListBloc>(context);
myListBloc.add(newItem);

3. 应用代码剖析工具进步代码品质

代码剖析工具,如 Flutter Analyzer 和 Lint,对于进步代码品质和升高 bug 和谬误的危险十分有帮忙。这些工具能够帮忙在潜在问题成为问题之前辨认它们,还能够提供改良代码构造和可读性的倡议。

以下是在 Flutter 中应用 Flutter Analyzer 的示例:

flutter analyze lib/

4. 应用自动化测试进步代码可靠性

自动化测试是构建大型应用程序的重要组成部分,因为它有助于确保代码牢靠并按预期执行。Flutter 为自动化测试提供了几个选项,包含单元测试、小部件测试和集成测试。

上面是应用 Flutter Test 包进行自动化测试的示例:

void main() {test('UserRepository returns a list of users', () {final userRepository = UserRepository();
    final result = userRepository.getUsers();
    expect(result, isInstanceOf<List<User>>());
  });
}

5. 应用 Flutter Inspector 进行调试

Flutter Inspector 是一个弱小的工具,用于调试 Flutter 应用程序。它容许开发人员检查和操作小部件树,查看性能指标等。Flutter Inspector 能够通过 Flutter DevTools 浏览器扩大或通过命令行拜访。

上面是应用 Flutter Inspector 进行调试的示例:

flutter run --debug

6. 提早加载和分页

一次获取和出现大量数据会显著影响性能。实现提早加载和分页以依据须要加载数据,特地是对于长列表或数据密集型视图。

// Bad Approach
// Fetch and load all items at once.
List<Item> allItems = fetchAllItems();

// Better Approach
// Implement lazy loading and pagination.
List<Item> loadItems(int pageNumber) {// Fetch and return data for the specific page number.}

// Use a ListView builder with lazy loading.
ListView.builder(
  itemCount: totalPages,
  itemBuilder: (context, index) {
    return FutureBuilder(future: loadItems(index),
      builder: (context, snapshot) {if (snapshot.connectionState == ConnectionState.done) {// Build your list item here.} else {return CircularProgressIndicator();
        }
      },
    );
  },
);

7. 放大图像大小

大型图像文件可能会升高利用的性能,尤其是在加载多个图像时。压缩和调整图像大小,以缩小其文件大小,而不会影响太多的品质。

示例:假如您有一个高分辨率的图像,但您只须要将其显示在应用程序中的较小容器中。您能够应用 flutter_image_compress 库来调整其大小,而不是应用原始的高分辨率图像。

import 'package:flutter_image_compress/flutter_image_compress.dart';

// Original image file
var imageFile = File('path/to/original/image.png');
// Get the image data
var imageBytes = await imageFile.readAsBytes();
// Resize and compress the image
var compressedBytes = await FlutterImageCompress.compressWithList(
  imageBytes,
  minHeight: 200,
  minWidth: 200,
  quality: 85,
);
// Save the compressed image to a new file
var compressedImageFile = File('path/to/compressed/image.png');
await compressedImageFile.writeAsBytes(compressedBytes);

8. 优化动画

防止应用可能影响利用性能的沉重或简单的动画,尤其是在较旧的设施上。明智地应用动画,并思考应用 Flutter 的内置动画,如 AnimatedContainerAnimatedOpacity 等。

// Bad Approach
// Using an expensive animation
AnimatedContainer(duration: Duration(seconds: 1),
  height: _isExpanded ? 300 : 1000,
  color: Colors.blue,
);
// Better Approach
// Using a simple and efficient animation
AnimatedContainer(duration: Duration(milliseconds: 500),
  height: _isExpanded ? 300 : 100,
  color: Colors.blue,
);

9. 优化应用程序启动工夫

通过优化初始化过程缩小利用的启动工夫。应用 flutter_native_splash 包在利用加载时显示闪屏,并将非必要组件的初始化提早到利用启动后。

10. 防止应用深树,而是创立一个独自的小部件

你不想持续滚动你的 IDE 与一千行代码。尝试创立一个独自的小部件。它将看起来洁净,易于重构。

//Bad
Column(
  children: [
    Container(//some lengthy code here),
    Container(//some another lengthy code),
    //some another lengthy code
  ],
)

//Good
Column(
  children: [FirstLengthyCodeWidget(),
    SecondLengthyCodeWidget(),
    //another lengthy code widget etc
  ],
)

11. 应用级联(..)

如果你刚开始应用 flutter,你可能还没有应用这个操作符,然而当你想在同一个对象上执行一些工作时,它十分有用。

//Bad
var paint = Paint();
paint.color = Colors.black;
paint.strokeCap = StrokeCap.round;
paint.strokeWidth = 5.0;

//Good
var paint = Paint()
  ..color = Colors.black
  ..strokeCap = StrokeCap.round
  ..strokeWidth = 5.0;

12. 应用开展运算符(…)

这是 dart 提供的另一个丑陋的操作符。您能够简略地应用此操作符执行许多工作,例如 if-else、退出列表等。

//Bad
@override
Widget build(BuildContext context) {
  bool isTrue = true;
  return Scaffold(
    body: Column(
      children: [isTrue ? const Text('One') : Container(),
        isTrue ? const Text('Two') : Container(),
        isTrue ? const Text('Three') : Container(),],
    ),
  );
}

//Good
@override
Widget build(BuildContext context) {
  bool isTrue = true;
  return Scaffold(
    body: Column(
      children: [if(isTrue)...[const Text('One'),
          const Text('Two'),
          const Text('Three')
        ]
      ],
    ),
  );
}

13. 防止应用硬编码的款式、装璜等

如果您在应用程序中应用硬编码的款式、装璜等,并且稍后决定更改这些款式。你会一个接一个地修复它们。

//Bad
Column(
  children: const [
    Text(
      'One',
      style: TextStyle(
        fontSize: 14,
        fontWeight: FontWeight.normal,
      ),
    ),
    Text(
      'Two',
      style: TextStyle(
        fontSize: 14,
        fontWeight: FontWeight.normal,
      ),
    ),
  ],
)

//Good
Column(
  children: [
    Text(
      'One',
      style: Theme.of(context).textTheme.subtitle1,
    ),
    Text(
      'Two',
      style: Theme.of(context).textTheme.subtitle1,
    ),
  ],
),

14. 小心应用 build()

防止应用过大的单个小部件和较大的 build() 函数。依据封装以及它们的变动形式将它们拆分为不同的小部件。

当在 State 对象上调用 setState() 时,所有派生小部件都会从新生成。因而,将 setState() 调用本地化到 UI 理论须要更改的子树局部。如果更改只蕴含在树的一小部分中,请防止在树的较高地位调用 setState()

让咱们看看这个例子,咱们心愿当用户按下图标时,只有这个图标的色彩会扭转。

因而,如果咱们在一个小部件中领有所有这些 UI,当按下图标时,它将更新整个 UI。咱们能够做的是将图标分隔为 StatefulWidget

之前

import 'package:flutter/material.dart';

class FidgetWidget extends StatefulWidget {const FidgetWidget({Key? key}) : super(key: key);

  @override
  _FidgetWidgetState createState() => _FidgetWidgetState();
}

class _FidgetWidgetState extends State<FidgetWidget> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('App Title'),
      ),
      body: Column(
        children: [Text('Some Text'),
          IconButton(onPressed: () => setState(() {// Some state change here}),
            icon: Icon(Icons.favorite),
          ),
        ],
      ),
    );
  }
}

之后

import 'package:flutter/material.dart';

class MyIconWidget extends StatefulWidget {const MyIconWidget({Key? key}) : super(key: key);

  @override
  _MyIconWidgetState createState() => _MyIconWidgetState();
}

class _MyIconWidgetState extends State<MyIconWidget> {
  @override
  Widget build(BuildContext context) {
    return IconButton(onPressed: () => setState(() {}),
      icon: Icon(Icons.favorite),
    );
  }
}

15. 应用 Widgets 而不是函数

您能够节俭 CPU 周期,并与构造函数一起应用,在须要时进行重建,还有更多益处(重复使用等 ……)。

//Bad
@override
Widget build(BuildContext context) {
  return Column(
    children: [_getHeader(),
      _getSubHeader(),
      _getContent()]
   );
}

//Good
@override
Widget build(BuildContext context) {
  return Column(
    children: [HeaderWidget(),
      SubHeaderWidget(),
      ContentWidget()]
  );
}

正如 Riverpod、Provider 和其余软件包的创建者 Remi Rousselet 所说。“类有更好的默认行为。应用办法的惟一益处就是能够少写一点代码。没有任何性能上的益处”。

16. 尽可能应用 final

应用 final 关键字能够大大提高利用的性能。当一个值被申明为 final 时,它只能被设置一次,尔后不会扭转。这意味着框架不须要常常查看更改,从而进步了性能。

final items = ["Item 1", "Item 2", "Item 3"];

在这个例子中,变量项被申明为 final,这意味着它的值不能被扭转。这进步了性能,因为框架不须要查看此变量的更改。

17. 尽可能应用 const

x = Container();
y = Container();
x == y // false

x = const Container();
y = const Container();
x == y // true

const widget 在编译时创立,因而在运行时速度更快。

18. 尽可能应用 const 构造函数

class CustomWidget extends StatelessWidget {const CustomWidget();

  @override
  Widget build(BuildContext context) {...}
}

当构建本人的小部件或应用 Flutter 小部件时。这有助于 Flutter 只重建应该更新的小部件。

19. 尽可能应用公有变量 / 办法

除非必要,否则尽可能应用 private 关键字。

//Bad
class Student {
  String name;
  String address;
  Student({
      required this.name,
      required this.address,
    });
  }
}

//Good
class Student{
  String _name;
  String _address;
  Student({
    required String name,
    required String address,
  }):
  _name = name,
  _address = address;
}

是的,与性能相比,它更像是 Dart 的最佳实际。然而,最佳实际能够在某种程度上进步性能,比方了解代码、升高复杂性等。

20. 应用 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)

它只是一个根本的元素小部件,简直没有老本。查看包 – nil。

21. 在 ListView 中对长列表应用 itemExtent

这有助于 Flutter 计算 ListView 滚动地位,而不是计算每个小部件的高度,并使滚动动画更具性能。

默认状况下,每个孩子都必须确定其范畴,这在性能方面是相当低廉的。显式设置该值能够节俭大量 CPU 周期。列表越长,应用此属性取得的速度就越快。

//Nope
final List<int> _listItems = <int>[1, 2, 3, 4, 5, 6, 7, 8, 9];

@override
Widget build(BuildContext context) {
  return ListView.builder(
    itemCount: _listItems.length,
    itemBuilder: (context, index) {var item = _listItems[index];
      return Center(child: Text(item.toString())
      );
    }
}

//Good
final List<int> _listItems = <int>[1, 2, 3, 4, 5, 6, 7, 8, 9];

@override
Widget build(BuildContext context) {
  return ListView.builder(
    itemExtent: 150,
    itemCount: _listItems.length,
    itemBuilder: (context, index) {var item = _listItems[index];
      return Center(child: Text(item.toString())
      );
    }
}

22. 防止将 AnimationController 与 setState 一起应用

这不仅会导致从新构建整个 UI 的动画部件,而且会使动画滞后。

void initState() {
  _controller = AnimationController(
    vsync: this,
    duration: Duration(seconds: 1),
  )..addListener(() => setState(() {}));
}

Column(
  children: [Placeholder(), // rebuilds
    Placeholder(), // rebuilds
    Placeholder(), // rebuilds
    Transform.translate( // rebuilds
      offset: Offset(100 * _controller.value, 0),
      child: Placeholder(),),
  ],
),

void initState() {
  _controller = AnimationController(
    vsync: this,
    duration: Duration(seconds: 1),
  );
  // No addListener(...)
}

AnimatedBuilder(
  animation: _controller,
  builder: (_, child) {
  return Transform.translate(offset: Offset(100 * _controller.value, 0),
    child: child,
    );
  },
  child: Placeholder(),),

23. 应用 Keys 减速 Flutter 性能

应用 Keys 时,Flutter 能更好地辨认小部件。这让咱们的性能进步了 4 倍。

// 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,

审慎:
ValueKey 会让你的代码看起来有点臃肿
GlobalKey 有点危险,但有时候值得。

24. 应用图像 ListView 时优化内存

ListView.builder(
  ...
  addAutomaticKeepAlives: false (true by default)
  addRepaintBoundaries: false (true by default)
);

ListView 无奈杀死屏幕上不可见的子代。如果子代具备高分辨率图像,就会耗费大量内存。

通过将这些选项设置为 false,可能会导致应用更多的 GPU 和 CPU 工作,但它能够解决咱们的内存问题,并且您将取得十分高性能的视图,而不会呈现显著的问题。

25. 应用 for/while 代替 foreach/map

如果你要解决大量的数据,应用正确的循环可能会影响你的性能。

26. 预缓存您的图像和图标

这取决于具体情况,但我个别会在主零碎中事后缓存所有图像。

对于图片

你不须要任何包装,只有用 -

precacheImage(AssetImage(imagePath),
  context
);

对于 SVG

您须要 flutter_svg

precachePicture(ExactAssetPicture(SvgPicture.svgStringDecoderBuilder, iconPath),
  context
);

27. 应用 SKSL 预热

flutter run --profile --cache-sksl --purge-persistent-cache
flutter build apk --cache-sksl --purge-persistent-cache

如果一个应用程序在第一次运行时有不稳固的动画,而后在雷同的动画中变得平滑,那么它很可能是因为着色器编译的不稳固。

28. 思考应用 RepaintBoundary

此小部件为其子小部件创立独自的显示列表,这能够进步特定状况下的性能。

29. 如果可能,请应用生成器命名的构造函数

Listview.builder()

生成器只会在屏幕上渲染显示的我的项目。即便看不到,也能显示所有的孩子。

30. 不要应用 ShrinkWrap 任何可滚动的小部件

测量内容问题。

31. 应用重载函数时应用 ISOLATE (隔离)

有些办法十分低廉,比方图像处理,它们可能会让应用程序在主线程中解冻。如果不想呈现这种状况,就应该思考应用隔离。

32. 不要为每一件小事应用隔离

但如果您在任何中央都应用隔离区,即便是最小的操作,您的应用程序也会变得十分蠢笨。因为生成隔离区的操作并不便宜。这须要工夫和资源。

33. 正确处理数据

不必要的内存应用会悄无声息地杀死应用程序外部的数据。因而,不要遗记解决数据。

34. 压缩数据以节俭内存

final response = await rootBundle.loadString('assets/en_us.json');

final original = utf8.encode(response);

final compressed = gzip.encode(original);
final decompress = gzip.decode(compressed);

final enUS = utf8.decode(decompress);

这样还能够节俭一些内存。

35. 放弃更新 Flutter

每一个版本的 Flutter 都会变得越来越快。因而,不要忘了及时更新您的 Flutter 版本,让您的作品更加精彩。

36. 在实在的设施上测试性能

始终在实在的设施(包含较旧型号)上测试利用的性能,以确定在模拟器或较新设施上可能不显著的任何性能问题。

37. 首选 StatelessWidget 优于 StatefulWidget

StatelessWidgetStatefulWidget 更快,因为顾名思义,它不须要治理状态。这就是为什么如果可能的话,你应该抉择它的起因。

抉择 StatefulWidget 时 …

你须要一个筹备性能 initState()

您须要应用 dispose() 解决资源

您须要应用 setState() 触发小部件重建

你的小部件有变动的变量(非 final)

在所有其余状况下,您应该首选 StatelessWidget

38. 不应用 OpacityWidget

当与动画一起应用时,Opacity 小部件可能会导致性能问题,因为 Opacity 小部件的所有子小部件也将在每个新帧中重建。在这种状况下,最好应用 AnimatedOpacity。如果要淡入图像,请应用 FadeInImage 小部件。如果您心愿有一个不透明度的色彩,请绘制一个不透明度的色彩。

//Bad
Opacity(opacity: 0.5, child: Container(color: Colors.red))
//Good
Container(color: Color.fromRGBO(255, 0, 0, 0.5))

39. 首选 SizedBox 而不是 Container

Container widget 非常灵活。例如,您能够自定义填充或边框,而无需将其嵌套在另一个小部件中。但如果你只须要一个具备肯定高度和宽度的盒子,最好应用 SizedBox 小部件。它能够成为常量,而 Container 不能。

要在 Row/Column, 中增加空白,请应用 SizedBox 而不是 Container

//NOPE
@override
Widget build(BuildContext context) {
  return Column(
    children: [Text(header),
      Container(height: 10),
      Text(subheader),
      Text(content)
    ]
  );
}

//YES~
@override
Widget build(BuildContext context) {
return Column(
  children: [Text(header),
      const SizedBox(height: 10),
      Text(subheader),
      Text(content)
    ]
  );
}

40. 不要应用剪裁

剪裁是一个十分低廉的操作,当你的利用变慢时应该防止。如果将剪切行为设置为 Clip.antiAliasWithSaveLayer,则代价会更高。试着寻找其余的办法来实现你的指标。例如,一个圆角边框的矩形能够用 borderRadius 属性来代替剪裁。

41. 应用 Offstage 小部件

Offstage 小部件容许您暗藏一个小部件,而无需将其从小部件树中移除。这对于进步性能十分有用,因为框架不须要重建暗藏的小部件。

Offstage(
  offstage: !showWidget,
  child: MyWidget(),)

在本例中,当 showWidget 变量为假时,Offstage 小部件用于暗藏 MyWidget 小部件。这通过缩小须要重建的小部件的数量来进步性能。

你有没有想过 OffstageOpacityVisibility widget 之间的区别?在这里你能够找到简短的解释。

在 Flutter 中,Offstage widget 用于在布局中暗藏子 widget,同时它依然是树的一部分。它可用于有条件地显示或暗藏子部件,而无需从新构建整个树。

Opacity widget 用于管制子 widget 的透明度。它采纳一个介于 0.0 和 1.0 之间的值,其中 0.0 示意齐全通明,1.0 示意齐全不通明。然而,重要的是要留神它可能会影响性能,因而仅在必要时应用它。

Visibility 小部件用于管制子小部件的可见性。它能够用于有条件地显示或暗藏子部件,而不用从新构建整个树。

这三个部件都用于管制子部件的显示,但它们的管制形式不同。Offstage 管制布局,Opacity 管制透明度,Visibility 管制可见性

42. 应用 WidgetsBinding.instance.addPostFrameCallback

在某些状况下,咱们须要在渲染帧后执行某些操作。既不要尝试应用任何提早函数,也不要创立自定义回调!咱们能够应用 WidgetsBinding.instance.addPostFrameCallback 办法来做到这一点。该回调将在框架渲染后调用,并通过防止不必要的重建来进步性能。

WidgetsBinding.instance.addPostFrameCallback((_) {//Perform the action here});

43. 应用 AutomaticKeepAliveClientMixin

当应用 ListViewGridView 时,能够屡次构建子对象。为了防止这种状况,咱们能够对子部件应用 AutomaticKeepAliveClientMixin。这将使子部件的状态放弃沉闷并进步性能。

class MyChildWidget extends StatefulWidget {
  @override
  _MyChildWidgetState createState() => _MyChildWidgetState();
}

class _MyChildWidgetState extends State<MyChildWidget> with AutomaticKeepAliveClientMixin {
  @override
  bool get wantKeepAlive => true;

  @override
  Widget build(BuildContext context) {return Text("I am a child widget");
  }
}

在本例中,MyChildWidget 类应用 AutomaticKeepAliveClientMixin mixin,wantKeepAlive 属性设置为 true。这将使 MyChildWidget 的状态放弃活动状态,并避免它被屡次重建,从而进步性能。

44. 应用 MediaQuery.sizeOf(context)

当您应用 MediaQuery.of(context).size 时,flutter 会将您的小部件与 MediaQuery 的大小相关联,这在代码库中屡次应用时可能会导致不必要的重建。通过应用 MediaQuery.sizeOf(context),您能够绕过这些不必要的重建,并加强利用的响应能力。也实用于其余 MediaQuery 办法,例如应用 .platformBrightnessOf(context) 而不是 .of(context).

好吧,然而如何测量它们呢?

要掂量 Dart/Flutter 应用程序的性能,能够使用性能视图。它提供了显示 Flutter 帧和工夫线事件的图表。

不要在调试模式下测量性能

有一种用于性能和内存测量的非凡模式,即 Profile 模式。您能够通过 Android Studio 或 Visual Studio Code 等 IDE 或执行以下 CLI 命令来运行它:

flutter run - profile

在调试模式下,应用程序未被优化,因而通常比其余模式运行得慢。

不要在模拟器中测量性能

运行编译后的应用程序查看性能问题时,请勿应用模拟器。模拟器无奈像实在设施一样再现真实世界的行为。在理论设施上运行时,您可能会发现一些并非真正的问题。模拟器也不反对配置文件模式。

论断局部

优化 Flutter 利用的性能对于提供无缝用户体验至关重要。通过施行这些提醒,您能够进一步优化 Flutter 利用的性能。请记住,性能优化是一个继续的过程,定期的性能剖析和测试对于确保您的利用放弃其高性能规范至关重要。


原文:https://medium.com/@panuj330/pushing-flutter-to-the-limit-per…

退出移动版