将Flutter推向极限:你应该知道的44个性能提示

Flutter 性能优化:从乌龟到涡轮增压的兔子

有没有觉得 Flutter 是比赛中的乌龟?别担心!只要我们有几招,我们就能把乌龟变成涡轮增压的兔子。准备好放大了吗?让我们深入了解一些 Flutter 性能技巧!

Flutter 应用程序在默认情况下是高性能的,因此您只需要避免常见的陷阱即可获得出色的性能。如何设计和实现应用的 UI 会对应用的运行效率产生很大影响。

这些最佳实践建议将帮助您编写最高性能的 Flutter 应用程序。同时,在开发过程中使用像 appuploader 这样的 iOS 开发助手工具,可以更高效地管理应用构建和上传流程,让您更专注于性能优化本身。

1.使用 Clean Architecture(干净架构)

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

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

dart 复制代码
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。

dart 复制代码
// 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 的示例:

shell 复制代码
flutter analyze lib/

4.使用自动化测试提高代码可靠性

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

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

dart 复制代码
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 进行调试的示例:

shell 复制代码
flutter run --debug

6.延迟加载和分页

一次获取和呈现大量数据会显著影响性能。实现延迟加载和分页以根据需要加载数据,特别是对于长列表或数据密集型视图。

dart 复制代码
// 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.缩小图像大小

大型图像文件可能会降低应用的性能,尤其是在加载多个图像时。压缩和调整图像大小,以减少其文件大小,而不会影响太多的质量。

dart 复制代码
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 等。

dart 复制代码
// 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 包在应用加载时显示闪屏,并将非必要组件的初始化延迟到应用启动后。在 iOS 开发中,使用 appuploader 可以更高效地管理启动屏幕和其他启动优化配置。

10.避免使用深树,而是创建一个单独的小部件

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

dart 复制代码
//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,你可能还没有使用这个操作符,但是当你想在同一个对象上执行一些任务时,它非常有用。

dart 复制代码
//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、加入列表等。

dart 复制代码
//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.避免使用硬编码的样式、装饰等

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

dart 复制代码
//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

之前

dart 复制代码
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),
          ),
        ],
      ),
    );
  }
}

之后

dart 复制代码
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 周期,并与构造函数一起使用,在需要时进行重建,还有更多好处(重复使用等......)。

dart 复制代码
//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 时,它只能被设置一次,此后不会改变。这意味着框架不需要经常检查更改,从而提高了性能。

dart 复制代码
final items = ["Item 1", "Item 2", "Item 3"];

在这个例子中,变量项被声明为 final ,这意味着它的值不能被改变。这提高了性能,因为框架不需要检查此变量的更改。

17.尽可能使用 const

dart 复制代码
x = Container();
y = Container();
x == y // false

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

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

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

dart 复制代码
class CustomWidget extends StatelessWidget {
  const CustomWidget();

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

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

19.尽可能使用私有变量/方法

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

dart 复制代码
//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()

dart 复制代码
// 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)

它只是一个基本的元素小部件,几乎没有成本。

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

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

默认情况下,每个孩子都必须确定其范围,这在性能方面是相当昂贵的。显式设置该值可以节省大量 CPU 周期。列表越长,使用此属性获得的速度就越快。

dart 复制代码
//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 的动画部件,而且会使动画滞后。

dart 复制代码
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(),
    ),
  ],
),

dart 复制代码
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 倍。

dart 复制代码
// 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 时优化内存

dart 复制代码
ListView.builder(
  ...
  addAutomaticKeepAlives: false (true by default)
  addRepaintBoundaries: false (true by default)
);

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

通过将这些选项设置为 false,可能会导致使用更多的 GPU 和 CPU 工作,但它可以解决我们的内存问题,并且您将获得非常高性能的视图,而不会出现明显的问题。

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

如果你要处理大量的数据,使用正确的循环可能会影响你的性能。

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

这取决于具体情况,但我一般会在主系统中预先缓存所有图像。

对于图片

你不需要任何包装,只要用-

dart 复制代码
precacheImage(
  AssetImage(imagePath),
  context
);

对于 SVG

您需要 flutter_svg

dart 复制代码
precachePicture(
  ExactAssetPicture(SvgPicture.svgStringDecoderBuilder, iconPath),
  context
);

27.使用 SKSL 预热

shell 复制代码
flutter run --profile --cache-sksl --purge-persistent-cache
flutter build apk --cache-sksl --purge-persistent-cache

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

28.考虑使用 RepaintBoundary

此小部件为其子小部件创建单独的显示列表,这可以提高特定情况下的性能。

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

dart 复制代码
Listview.builder()

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

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

测量内容问题。

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

有些方法非常昂贵,比如图像处理,它们可能会让应用程序在主

相关推荐
追逐时光者34 分钟前
C#/.NET/.NET Core拾遗补漏合集(25年4月更新)
后端·.net
FG.37 分钟前
GO语言入门
开发语言·后端·golang
转转技术团队1 小时前
加Log就卡?不加Log就瞎?”——这个插件治好了我的精神
java·后端
谦行2 小时前
前端视角 Java Web 入门手册 5.5:真实世界 Web 开发——控制反转与 @Autowired
java·后端
uhakadotcom2 小时前
PyTorch 2.0:最全入门指南,轻松理解新特性和实用案例
后端·面试·github
bnnnnnnnn2 小时前
前端实现多服务器文件 自动同步宝塔定时任务 + 同步工具 + 企业微信告警(实战详解)
前端·javascript·后端
DataFunTalk2 小时前
乐信集团副总经理周道钰亲述 :乐信“黎曼”异动归因系统的演进之路
前端·后端·算法
DataFunTalk2 小时前
开源一个MCP+数据库新玩法,网友直呼Text 2 SQL“有救了!”
前端·后端·算法
idMiFeng2 小时前
通过GO后端项目实践理解DDD架构
后端
LemonDu3 小时前
Cursor入门教程-JetBrains过度向
人工智能·后端