将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 (隔离)

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

相关推荐
IT_陈寒10 分钟前
我用这5个JavaScript性能优化技巧,让页面加载速度提升了60%
前端·人工智能·后端
清空mega12 分钟前
Flask入门学习指南
后端·python·flask
风象南22 分钟前
SpringBoot 时间轮实现延时任务
后端
Victor35626 分钟前
Redis(93)Redis的数据加密机制是什么?
后端
Victor35628 分钟前
Redis(92)如何配置Redis的ACL?
后端
有你有我OK1 小时前
springboot Admin 服务端 客户端配置
spring boot·后端·elasticsearch
xiaoopin3 小时前
简单的分布式锁 SpringBoot Redisson‌
spring boot·分布式·后端
你的人类朋友8 小时前
设计模式有哪几类?
前端·后端·设计模式
Yeats_Liao9 小时前
Go Web 编程快速入门 10 - 数据库集成与ORM:连接池、查询优化与事务管理
前端·数据库·后端·golang
你的人类朋友10 小时前
适配器模式:适配就完事了bro!
前端·后端·设计模式