Flutter 应用程序性能优化建议

视频

https://www.bilibili.com/video/BV1ht421L7mP/

前言

原文 https://ducafecat.com/blog/boosting-flutter-performance-top-tips-for-developers

Flutter应用程序默认已经具有良好的性能,因此您只需要避免常见的陷阱,就可以获得出色的性能。

您设计和实现应用程序的用户界面的方式可能会对其运行效率产生重大影响。

本文这些最佳实践建议将帮助您编写性能最佳的Flutter应用程序。

那么让我们开始吧!

正文

代码结构拆分合理

干净架构

图片[1]-Flutter 应用程序性能优化建议-山海云端论坛
图片[2]-Flutter 应用程序性能优化建议-山海云端论坛

使用状态管理

需要一套规范来耦合所有的内容

图片[3]-Flutter 应用程序性能优化建议-山海云端论坛

常见的优秀状态管理有:

  • provider
  • bloc
  • getx
  • riverpod

可以看下各种状态管理文章 https://ducafecat.com/blog/flutter-state-management-libraries-2024

使用代码分析工具

代码分析工具,如Flutter分析器和Lint,对于提高代码质量和减少错误和漏洞的风险非常有帮助。这些工具可以帮助识别潜在问题,防止它们成为问题,并提供改进代码结构和可读性的建议。

lutter analyze lib/

使用 Flutter Inspector 进行调试

flutter run --debug

图片[4]-Flutter 应用程序性能优化建议-山海云端论坛

之前录过一个 dev tools 性能调优的视频

https://www.bilibili.com/video/BV1Tb4y1p7t9

https://ducafecat.tech/2022/03/17/2022/flutter-devtools-performance/

懒加载和分页

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

ListView.builder 方式

List<Item> loadItems(int pageNumber) {
}

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();

List<Item> loadItems(int pageNumber) {
}

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();

图片[5]-Flutter 应用程序性能优化建议-山海云端论坛

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

预缓存您的图片和图标

图片

precacheImage(  AssetImage(imagePath),   context);

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

使用SKSL预热

如果一个应用在第一次运行时的动画不流畅,但后来相同的动画变得流畅,那很可能是由于着色器编译引起的不流畅。flutter run --profile --cache-sksl --purge-persistent-cache
flutter build apk --cache-sksl --purge-persistent-cache

使用 RepaintBoundary

RepaintBoundary是一个 Widget ,用于将其子部件的绘制内容分离为单独的绘制层。这样做的主要目的是减少不必要的重绘操作,提高应用程序的性能。当RepaintBoundary包裹一个子部件时,该子部件及其所有子部件将被视为一个整体,即使其中的其他部分发生重绘,RepaintBoundary内的内容也不会重绘。

RepaintBoundary的主要作用包括:

  1. 减少重绘范围:通过将子部件包裹在RepaintBoundary中,可以将其视为一个整体,仅在该部件内部发生重绘时才重新绘制,而不会影响到其他部分。
  2. 性能优化:避免不必要的重绘操作,可以提高应用程序的性能,特别是在具有复杂界面或动态内容的情况下。
  3. 避免全局重绘:在某些情况下,只需要更新特定部分的UI,而不是整个界面。通过使用RepaintBoundary,可以限制重绘的范围,避免全局重绘。
  4. 边界控制:可以通过RepaintBoundary来控制重绘的边界,确保只在需要时才进行重绘操作,而不会影响到其他部分。

RepaintBoundary是一个有用的工具,可以帮助优化Flutter应用程序的性能,特别是在需要控制重绘范围和避免不必要重绘操作的情况下。在开发复杂界面或需要动态更新的应用程序时,合理使用RepaintBoundary可以提高应用程序的性能和用户体验。class RepaintBoundaryExample extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return RepaintBoundary(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          Text('This is inside RepaintBoundary'),
          SizedBox(height: 20),
          CustomPaint(
            size: Size(200, 200),
            painter: MyPainter(),
          ),
        ],
      ),
    );
  }
}

class MyPainter extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    Paint paint = Paint()
      ..color = Colors.blue
      ..strokeWidth = 5
      ..style = PaintingStyle.stroke;

    canvas.drawRect(Rect.fromLTWH(50, 50, 100, 100), paint);
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) {
    return false;
  }
}

使用 Listview.builder

Listview.builder()

用了之后不出现在屏幕上的元素,不渲染。

不要使用 ShrinkWrap 来包裹可滚动 Widget

ShrinkWrap 动态确定子组件大小。

包裹滚动组件后可能有布局错误、性能问题。

处理高消耗操作时用 isolates

比如处理非常大的 json 文件、视频压缩。

这样不会卡主线程。

可以用一些 Dart 包简化代码。

https://pub-web.flutter-io.cn/packages/flutter_isolate

不要过度使用 isolates

如果你在每个最小的操作中都使用 isolates,你的应用程序可能会非常卡顿。

这是因为生成一个 isolates 并不是一项廉价的操作。它需要时间和资源。

释放你不用的内存数据

比如你载入一个图片数据进行加工,如加文字、加二维码,不用的时候请释放。

压缩数据处理

为了节省内存,请压缩您的数据。

比如你载入了百兆的 json 文件,你可以压缩起来放在内存中。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);

保持 Flutter 新稳定版本

在每个版本中,Flutter都变得越来越快。

所以不要忘记及时更新你的Flutter版本,并继续创作出令人惊艳的作品!

注意用稳定版。

https://docs.flutter.dev/release/archive?tab=macos

请多准备几台真机调试

始终在真实设备上测试您的应用程序性能,包括较旧的型号,以便发现在模拟器或较新设备上可能不明显的性能问题。

使用StatelessWidget而不是StatefulWidget

一个 StatelessWidget 比一个 StatefulWidget 更快,因为它不需要像其名称所暗示的那样管理状态。

所以如果可能的话,你应该优先选择它。

不要使用OpacityWidget

Opacity Widget在与动画一起使用时可能会导致性能问题,因为 Opacity Widget的所有子Widget都会在每个新帧中重新构建。在这种情况下,最好使用 AnimatedOpacity 。如果您想要淡入一张图片,请使用FadeInImageWidget。如果您想要具有不透明度的颜色,请绘制具有不透明度的颜色。//不推荐
Opacity(opacity: 0.5, child: Container(color: Colors.red))

//推荐
Container(color: Color.fromRGBO(255, 0, 0, 0.5))

使用SizedBox而不是Container

一个 Container Widget非常灵活。例如,您可以自定义填充或边框,而无需将其嵌套在另一个Widget中。但是,如果您只需要一个具有特定高度和宽度的框,最好使用 SizedBox Widget。它可以被设置为const,而 Container 则不行。

在 Row/Column, 中添加空格时,更倾向于使用 SizedBox 而不是 Container 。@override
Widget build(BuildContext context) {
return Column(
  children: [ 
      Text(header),
      const SizedBox(height: 10), 
      Text(subheader), 
      Text(content)
    ]
  ); 
}

不要用 Clip

Clip是一项非常昂贵的操作,当你的应用程序变慢时应该避免使用。如果Clip行为设置为 Clip.antiAliasWithSaveLayer ,它的代价会更高。尝试找到其他不需要Clip的方法来实现你的目标。例如,可以使用 borderRadius 属性来创建带有圆角边框的矩形,而不是使用Clip。Container(
  width: 100,
  height: 100,
  decoration: BoxDecoration(
    borderRadius: BorderRadius.circular(50),
    image: DecorationImage(
      image: NetworkImage('https://example.com/image.jpg'),
      fit: BoxFit.cover,
    ),
  ),
)

使用Offstage

OffstageWidget允许您隐藏一个Widget,而不需要从Widget树中移除它。这对于提高性能很有用,因为框架不需要重新构建隐藏的Widget。Offstage(
  offstage: !showWidget,
  child: MyWidget(),
)

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

Opacity Widget用于控制子Widget的透明度。它接受一个介于0.0和1.0之间的值,其中0.0表示完全透明,1.0表示完全不透明。然而,重要的是要注意它可能会影响性能,所以只在必要时使用。

Visibility Widget用于控制子Widget的可见性。它可以用于有条件地显示或隐藏子Widget,而无需重新构建整个树。

所有三个Widget都用于控制子Widget的显示,但它们的方式不同。

Offstage控制布局,Opacity控制透明度,Visibility控制可见性。

使用 addPostFrameCallback

在某些情况下,我们需要在帧渲染后执行某些操作。不要尝试使用任何延迟函数,也不要创建自定义回调!我们可以使用 WidgetsBinding.instance.addPostFrameCallback 方法来实现。这个回调将在帧渲染后被调用,并通过避免不必要的重建来提高性能。WidgetsBinding.instance.addPostFrameCallback((_) {
 // ...
});

使用 AutomaticKeepAliveClientMixin

当使用 ListView 或 GridView 时,子部件可以被多次构建。为了避免这种情况,我们可以使用 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 混入,并且 wantKeepAlive 属性被设置为 true 。这将保持 MyChildWidget 的状态,并防止它被多次重建,从而提高性能。

避免使用 MediaQuery.of(context).size

当你在Flutter中使用MediaQuery.of(context).size时,Flutter会将你的小部件与MediaQuery的大小相关联。这意味着每次调用MediaQuery.of(context).size时,Flutter会检测MediaQuery的大小是否发生变化,从而可能导致不必要的重建(rebuilds)。

使用MediaQuery.sizeOf(context)来避免这些不必要的重建,从而提高应用程序的响应性。通过使用MediaQuery.sizeOf(context),你可以绕过与MediaQuery大小相关的重建过程,从而减少不必要的性能开销。

类似的优化方法也适用于其他MediaQuery方法。举例来说,建议使用MediaQuery.platformBrightnessOf(context)而不是MediaQuery.of(context).platformBrightness,以避免不必要的重建,从而提高应用的响应性。

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

一个用于性能和内存测量的特殊模式,即Profile模式。您可以通过Android Studio或Visual Studio Code等IDE运行它,也可以通过执行以下CLI命令来运行:flutter run -profile

不要在模拟器中测量性能

多用真机性能调试

© 版权声明
THE END
喜欢就支持一下吧
点赞13 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容