Flutter性能优化技巧

Flutter 应用程序默认是高性能的,因此只需避免常见的性能陷阱即可获得出色的性能。如何设计和实现应用程序的 UI 会对应用程序的运行效率产生重大影响。以下最佳实践建议将帮助您编写尽可能性能最高的 Flutter 应用程序。

1. 使用简洁的架构

清洁架构是一种强调关注点分离和独立测试的软件设计模式。这种模式鼓励将应用程序逻辑分成不同的层,每个层负责一组特定的任务。干净的架构非常适合大型应用程序,因为它提供了清晰的关注点分离并使得测试变得更加容易。 下面是Flutter的一份简洁架构实现:

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、GetX 等状态管理库。

3.使用代码分析工具来提高代码质量

代码分析工具(例如 Flutter Analyzer 和 Lint)对于提高代码质量和降低错误和错误的风险非常有帮助。这些工具可以帮助在潜在问题成为问题之前识别它们,还可以提供改进代码结构和可读性的建议。运行以下命令分析:

flutter analyze lib/

4. 使用自动化测试来保证代码可靠性

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

5.使用Flutter Inspector进行调试

Flutter Inspector 是用于调试 Flutter 应用程序的强大工具。它允许开发人员检查和操作小部件树、查看性能指标等等。可以通过 Flutter DevTools 浏览器扩展或通过命令行访问 Flutter Inspector。运行以下命令调试:

flutter run --debug

6. 延迟加载和分页

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

7. 减小图像尺寸

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

示例:假设有一个高分辨率的图像,只需要将其显示在应用程序中的较小容器中。您可以使用flutter_image_compress库调整其大小,而不是使用原始的高分辨率图像。

dart 复制代码
import 'package:flutter_image_compress/flutter_image_compress.dart';  
  
// 原始图像文件  
var imageFile = File('path/to/original/image.png');  
// 获取图像数据  
var imageBytes = await imageFile.readAsBytes();  
// 调整图像  
var compressedBytes = await FlutterImageCompress.compressWithList(  
    imageBytes,  
    minHeight: 200,  
    minWidth: 200,  
    quality: 85,  
);  
// 将压缩图像保存到一个新文件  
var compressedImageFile = File('path/to/compressed/image.png');  
await compressedImageFile.writeAsBytes(compressedBytes);

8. 优化动画

避免使用可能影响应用程序性能的繁重或复杂的动画,尤其是在较旧的设备上。明智地使用动画并考虑使用 Flutter 的内置动画如AnimatedContainerAnimatedOpacity等。

9、优化App启动时间

通过优化初始化过程来减少应用程序的启动时间。使用flutter_native_splash库在应用程序加载时显示启动屏幕,并将非必要组件的初始化延迟到应用程序启动后。

10. 避免使用大型组件,而是创建一个个单独的小部件

您不应该创建一千行代码的组件,尝试创建一个个单独的小组件来组合成页面。它将看起来干净并且易于重构。

dart 复制代码
//Bad  
Column(  
    children: [  
        Container(  
            ...  
        ),  
        Container(  
            ... 
        ),   
    ],  
)  
  
//Good  
Column(  
    children: [  
        FirstWidget(),  
        SecondWidget(),  
        ...  
    ],  
)

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

13. 谨慎使用 build()

避免具有大功能的过大的单个小部件build()。根据封装以及它们的变化方式将它们分成不同的小部件。

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

// Good
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),  
        );  
    }  
}

14. 使用小部件而不是函数

您可以节省 CPU 周期并与 const 构造函数一起使用,以便仅在需要时进行重建,并且还有更多好处(重用等......)

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所说。"类有更好的默认行为。 方法的唯一好处是编写的代码要少一点。没有任何功能上的好处。 "

15. 尽可能使用final

使用final关键字可以极大地提高应用程序的性能。当一个值被声明时,final它只能设置一次,此后不会更改。这意味着框架不需要不断检查更改,从而提高性能。

16. 尽可能使用 const

如果组件已经定义,您可以使用相同的小部件来节省 RAM。const widgets在编译时创建,因此运行时速度更快。

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

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

18. 尽可能使用私有变量、方法

private更像是 dart 最佳实践而不是提升性能。但是,最佳实践可以在某种程度上提高性能,例如理解代码、降低复杂性等。

19.使用nil代替const Container()

nil是一个基本的 Element Widget,几乎不需要任何成本。

20.在ListView中使用itemExtent来显示长列表

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

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

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

21.避免将AnimationController与setState一起使用

dart 复制代码
void initState() {  
    _controller = AnimationController(  
        vsync: this,  
        duration: Duration(seconds: 1),  
            )..addListener(() => setState(() {}));  
}  
  
Column(  
    children: [  
        Placeholder(), // 重建  
        Placeholder(), // 重建  
        Placeholder(), // 重建  
        Transform.translate( // 重建  
            offset: Offset(100 * _controller.value, 0),  
            child: Placeholder(),  
        ),  
    ],  
),

它会导致重建整个 UI,而不仅仅是动画小部件,并使动画变得滞后。改为:

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

22. 使用 Keys 加速 Flutter 性能

ValueKey 会让你的代码看起来有点臃肿

GlobalKey 有点危险,但有时是值得的。

dart 复制代码
// Bad、  
return value ? const SizedBox() : const Placeholder(),  
// Good  
return value ? const SizedBox(key: ValueKey('SizedBox')) : const Placeholder(key: ValueKey('Placeholder')),  
----------------------------------------------  
// Bad  
final inner = SizedBox();  
return value ? SizedBox(child: inner) : inner,  
// Good  
final global = GlobalKey();  
final inner = SizedBox(key: global);  
return value ? SizedBox(child: inner) : inner,

23.优化ListView中使用图片时的内存

ListView无法去除图片,只是在屏幕上看不到。如果子元素有高分辨率图像,它会导致消耗大量内存。通过将这些选项设置为 false,可能会导致使用更多 GPU 和 CPU 工作,但它可以解决我们的内存问题,并且将获得非常高性能的视图,而不会出现明显的问题。

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

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

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

25. 预先缓存图像和图标

这取决于运用场景,但通常预先缓存 main 中的所有图像。

26. 使用SKSL

如果APP在第一次运行期间动画出现卡顿,但随后同一动画变得平滑,则很可能是由于着色器编译卡顿造成的。

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

27.考虑使用RepaintBoundary

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

28. 如果可能,使用构建器命名的构造函数

Listview.builder()

构建器仅渲染屏幕上显示的项目。如果不使用构建器,即使看不到,也会渲染所有子级。

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

测量内容问题

30. 使用重函数时使用ISOLATE

有些方法非常耗费性能或者时间,例如图像处理,它们可能会在主线程中工作时冻结您的应用程序。如果不希望出现这种情况,应该考虑使用ISOLATE。

31. 不要在每件小事上都使用ISOLATE

隔离很棒,当你完成繁重的任务时它们非常有帮助。但如果你】到处使用,即使是最小的操作,你的应用程序也会变得非常卡顿。只是因为产生一个隔离体并不是那么便宜的操作来完成。这需要时间和资源。

32. 正确处理数据

不必要的内存使用会默默地杀死应用程序。因此,不要忘记处理无用数据。

33.为了内存而压缩数据

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

34. 及时了解 Flutter 的最新动态

Flutter 一直在不断更新改进,在某些情况下,由于某些功能不可用,我可能会使用一些第三方插件。但在未来的某些更新中,Flutter 可能已经解决了这个问题,并有一个内置的解决方案。在这种情况下,使用默认的 Flutter 解决方案比使用第三方方插件是一个很好的做法。

35. 在真实设备上测试性能

开发过程中尽可能始终在真实设备(包括旧型号)上测试应用程序的性能,以识别在模拟器或较新设备上可能不明显的任何性能问题。

36.不要使用OpacityWidget

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

37. 更多的 SizedBox 而不是 Container

Container部件非常灵活。例如,可以自定义填充或边框,而无需将其嵌套在另一个小部件中。但如果你只需要一个具有一定高度和宽度的盒子,最好使用SizedBox小部件。它可以被设为 const,但Container不能。

38.不要使用Clip

裁剪是一项非常昂贵的操作,当应用程序变慢时应该避免。如果裁剪行为设置为Clip.antiAliasWithSaveLayer,它会变得更加昂贵。尝试寻找其他方法来实现您的目标而不是裁剪。例如,可以使用该borderRadius属性来完成具有圆角边框的矩形,而不是剪切。

39.使用后台小部件

Offstage 小部件允许隐藏小部件,而无需将其从小部件树中删除。这对于提高性能很有用,因为框架不需要重建隐藏的小部件。

dart 复制代码
Offstage(  
    offstage: !showWidget,  
    child: MyWidget(),  
)

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

Opacity部件用于控制子小部件的透明度。它采用 0.0 和 1.0 之间的单个值,其中 0.0 是完全透明的,1.0 是完全不透明的。但是,请务必注意它可能会影响性能,因此仅在必要时使用它。

Visibility部件用于控制子小部件的可见性。它可用于有条件地显示或隐藏子窗口小部件,而无需重建整个树。

所有三个小部件都用于控制子小部件的显示,但它们以不同的方式进行。Offstage 控制布局,Opacity 控制透明度,Visibility 控制可见性。

40.使用WidgetsBinding.instance.addPostFrameCallback

在某些情况下,我们需要在帧渲染后执行一些操作。既不要尝试使用任何延迟函数,也不要创建自定义回调!我们可以使用WidgetsBinding.instance.addPostFrameCallback方法来做到这一点。此回调将在帧渲染后调用,并通过避免不必要的重建来提高性能。

41.使用AutomaticKeepAliveClientMixin

当使用ListViewGridView时,可以多次构建子项。为了避免这种情况,我们可以使用AutomaticKeepAliveClientMixin子窗口小部件。这将使子部件保持活动状态并提高性能。

dart 复制代码
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类使用AutomaticKeepAliveClientMixinmixin,并且wantKeepAlive属性设置为true。这将保持活动状态MyChildWidget并防止其被多次重建,从而提高性能。

44.使用MediaQuery.sizeOf(context)

当您使用MediaQuery.of(context).size时,flutter 会将您的小部件与 MediaQuery 的大小相关联,这在代码库中多次使用时可能会导致不必要的重建。通过使用MediaQuery.sizeOf(context),您可以绕过这些不需要的重建并增强应用程序的响应能力。也适用于其他 MediaQuery 方法,例如.platformBrightnessOf(context)使用.of(context).

测试性能

不要在调试模式下测量性能,有一种用于性能和内存测量的特殊模式,即 Profile 模式。可以通过 Android Studio 或 Visual Studio Code 等 IDE 或执行以下 CLI 命令来运行它:

flutter run - profile

在调试模式下,应用程序未进行优化,因此通常比其他模式下运行速度慢。

不要在模拟器中测量性能,当您运行已编译的应用程序来检查性能问题时,请勿使用模拟器。模拟器无法像真实设备一样重现真实世界的行为。在实际设备上执行时,您可能会注意到一些并非真正问题的问题。模拟器也不支持配置文件模式。

相关推荐
小政爱学习!17 分钟前
封装axios、环境变量、api解耦、解决跨域、全局组件注入
开发语言·前端·javascript
魏大帅。22 分钟前
Axios 的 responseType 属性详解及 Blob 与 ArrayBuffer 解析
前端·javascript·ajax
花花鱼28 分钟前
vue3 基于element-plus进行的一个可拖动改变导航与内容区域大小的简单方法
前端·javascript·elementui
wk灬丨29 分钟前
Android Kotlin Flow 冷流 热流
android·kotlin·flow
千雅爸爸29 分钟前
Android MVVM demo(使用DataBinding,LiveData,Fresco,RecyclerView,Room,ViewModel 完成)
android
k093332 分钟前
sourceTree回滚版本到某次提交
开发语言·前端·javascript
EricWang13581 小时前
[OS] 项目三-2-proc.c: exit(int status)
服务器·c语言·前端
September_ning1 小时前
React.lazy() 懒加载
前端·react.js·前端框架
web行路人1 小时前
React中类组件和函数组件的理解和区别
前端·javascript·react.js·前端框架
晨曦_子画1 小时前
编程语言之战:AI 之后的 Kotlin 与 Java
android·java·开发语言·人工智能·kotlin