首先 Flutter 是一个非常高性能的框架,因此大多时候不需要开发者做出特殊的处理,只需要避免常见的性能问题即可获得高性能的应用程序。

重建最小化原则

在调用 setState() 方法重建组件时,一定要最小化重建组件,没有变化的组件不要重建,看下面的Demo,这是一个设置页面,

import 'package:flutter/material.dart';

class SettingDemo extends StatefulWidget {
@override
_SettingDemoState createState() => _SettingDemoState();
} class _SettingDemoState extends State<SettingDemo> { Widget _item(
{IconData iconData, Color iconColor, String title, Widget suffix}) {
return Container(
height: 45,
child: Row(
children: <Widget>[
SizedBox(
width: 30,
),
Icon(
iconData,
color: iconColor,
),
SizedBox(
width: 30,
),
Expanded(
child: Text('$title'),
),
suffix,
SizedBox(
width: 15,
),
],
),
);
} bool _switchValue = false; @override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
_item(
iconData: Icons.notifications,
iconColor: Colors.blue,
title: '是否允许4G网络下载',
suffix: Switch(
value: _switchValue,
onChanged: (value) {
setState(() {
_switchValue = value;
});
}),
),
Divider(),
_item(
iconData: Icons.notifications,
iconColor: Colors.blue,
title: '消息中心',
suffix: Text(
'12条',
style: TextStyle(color: Colors.grey.withOpacity(.5)),
),
),
Divider(),
_item(
iconData: Icons.thumb_up,
iconColor: Colors.green,
title: '我赞过的',
suffix: Text(
'121篇',
style: TextStyle(color: Colors.grey.withOpacity(.5)),
),
),
Divider(),
_item(
iconData: Icons.grade,
iconColor: Colors.yellow,
title: '收藏集',
suffix: Text(
'2个',
style: TextStyle(color: Colors.grey.withOpacity(.5)),
),
),
Divider(),
_item(
iconData: Icons.account_balance_wallet,
iconColor: Colors.blue,
title: '我的钱包',
suffix: Text(
'10万',
style: TextStyle(color: Colors.grey.withOpacity(.5)),
),
),
],
);
}
}

注意看上图右边下半部分,点击切换开关的时候,所有的组件全部重建了,理想情况下,应该只是 Switch 组件进行切换,因此将 Switch 组件进行封装:

class _SwitchWidget extends StatefulWidget {
final bool value; const _SwitchWidget({Key key, this.value}) : super(key: key); @override
__SwitchWidgetState createState() => __SwitchWidgetState();
} class __SwitchWidgetState extends State<_SwitchWidget> {
bool _value; @override
void initState() {
_value = widget.value;
super.initState();
} @override
Widget build(BuildContext context) {
return Switch(
value: _value,
onChanged: (value) {
setState(() {
_value = value;
});
},
);
}
}

使用:

_item(
iconData: Icons.notifications,
iconColor: Colors.blue,
title: '是否允许4G网络下载',
suffix: _SwitchWidget(
value: false,
),
)

此时看到重建的组件只有 _SwitchWidgetSwitch 组件,提高了性能。

如果 Switch 组件的状态改变也会改变其它组件的状态,这是典型的组件间通信,这种情况下可以使用 InheritedWidget,但更建议使用状态管理框架(比如 Provider 等),而不是将其父组件改变为StatefulWidget。

尽量不要将整个页面定义为 StatefulWidget 组件,因为一旦重建将重建此页面下所有的组件,尤其是 Switch 、Radio等组件状态的改变导致的重建,强烈建议对其进行封装。

这里有一个误区,有些人认为,将组件拆分为方法可以减少重建,就比如上面的例子,将 _SwitchWidget 组件改变为方法,该方法返回 Switch 组件,这是错误的,此种方式并不能减少重建, 但是将一个组件拆分为多个小组件是可以减少重建的,就像上面的例子,将需要重建的 Switch 封装为一个单独的 StatefulWidget 组件,避免了其他不必要的重建。

强烈建议:在组件前加上 const

在组件前加上 const ,相当于对此组件进行了缓存,下面是未加 const 的代码:

class ConstDemo extends StatefulWidget {
@override
_ConstDemoState createState() => _ConstDemoState();
} class _ConstDemoState extends State<ConstDemo> {
@override
Widget build(BuildContext context) {
return Center(
child: Column(
children: [
Text('老孟'),
RaisedButton(onPressed: (){
setState(() { });
})
],
),
);
}
}

Text('老孟') 组件加上 const

const Text('老孟'),

对比两次 Text 组件的重建情况,加上 const 后,未重建。

避免更改组件树的结构和组件的类型

有如下场景,有一个 Text 组件有可见和不可见两种状态,代码如下:

bool _visible = true;

@override
Widget build(BuildContext context) {
return Center(
child: Column(
children: [
if(_visible)
Text('可见'),
Container(),
],
),
);
}

可见时的组件树:

不可见时的组件树:

两种状态组件树结构发生变化,应该避免发生此种情况,优化如下:

Center(
child: Column(
children: [
Visibility(
visible: _visible,
child: Text('可见'),
),
Container(),
],
),
)

此时不管是可见还是不可见状态,组件树都不会发生变化,如下:

还有一种情况是根据不同的条件构建不同的组件,如下:

bool _showButton = true;

@override
Widget build(BuildContext context) {
return Center(
child: Column(
children: [
_showButton ? RaisedButton(onPressed: null) : Text('不显示'),
Container(),
],
),
);
}

设置为 true 时的组件树结构:

设置为 false 时的组件树结构:

看到左侧子节点由 RaisedButton 变为了 Text

上面的情况组件树发生了更改,不管是类型发生更改,还是深度发生更改,如果无法避免,那么就将变化的组件树封装为一个 StatefulWidget 组件,且设置 GlobalKey,如下:

封装变化的部分:

class ChildWidget extends StatefulWidget {
const ChildWidget({Key key}) : super(key: key); @override
_ChildWidgetState createState() => _ChildWidgetState();
} class _ChildWidgetState extends State<ChildWidget> {
bool _showButton = true; @override
Widget build(BuildContext context) {
return _showButton ? RaisedButton(onPressed: null) : Text('不显示');
}
}

构建:

class ConstDemo extends StatefulWidget {
@override
_ConstDemoState createState() => _ConstDemoState();
} class _ConstDemoState extends State<ConstDemo> {
@override
Widget build(BuildContext context) {
return Center(
child: Column(
children: [
ChildWidget(key: GlobalKey(),),
Container(),
],
),
);
}
}

虽然通过 GlobalKey 提高了上面案例的性能,但我们千万不要乱用 GlobalKey,因为管理 GlobalKey 的成本很高,所以其他需要使用 Key 的地方建议考虑使用 Key, ValueKey, ObjectKey, 和 UniqueKey

关于 GlobalKey 的相关说明参考:https://api.flutter.dev/flutter/widgets/GlobalKey-class.html

关于ListView 的优化

ListView是我们最常用的组件之一,用于展示大量数据的列表。如果展示大量数据请使用 ListView.builder 或者 ListView.separated,千万不要直接使用如下方式:

ListView(
children: <Widget>[
item,item1,item2,...
],
)

这种方式一次加载所有的组件,没有“懒加载”,消耗极大的性能。

ListView 中 itemExtent 属性对动态滚动到性能提升非常大,比如,有2000条数据展示,点击按钮滚动到最后,代码如下:

class ListViewDemo extends StatefulWidget {
@override
_ListViewDemoState createState() => _ListViewDemoState();
} class _ListViewDemoState extends State<ListViewDemo> {
ScrollController _controller; @override
void initState() {
super.initState();
_controller = ScrollController();
} @override
Widget build(BuildContext context) {
return Stack(
children: [
ListView.builder(
controller: _controller,
itemBuilder: (context, index) {
return Container(
height: 80,
alignment: Alignment.center,
color: Colors.primaries[index % Colors.primaries.length],
child: Text('$index',style: TextStyle(color: Colors.white,fontSize: 20),),
);
},
itemCount: 2000,
),
Positioned(
child: RaisedButton(
child: Text('滚动到最后'),
onPressed: () {
_controller.jumpTo(_controller.position.maxScrollExtent);
},
))
],
);
}
}

耗时在2秒左右,加上 itemExtent 属性,修改如下:

ListView.builder(
controller: _controller,
itemBuilder: (context, index) {
return Container(
height: 80,
alignment: Alignment.center,
color: Colors.primaries[index % Colors.primaries.length],
child: Text('$index',style: TextStyle(color: Colors.white,fontSize: 20),),
);
},
itemExtent: 80,
itemCount: 2000,
)

优化后瞬间跳转到底部。

这是因为不设置 itemExtent 属性,将会由子组件自己决定大小,大量的计算导致UI堵塞。

关于 AnimatedBuilder TweenAnimationBuilder 的优化

这里说的是向AnimatedBuilder 、TweenAnimationBuilder 等一类的组件的问题,这些组件都有一个共同点,带有 builder 且其参数重有 child

以 AnimatedBuilder 为例,如果 builder 中构建的树中包含与动画无关的组件,将这些无关的组件当作 child 传递到 builder 中比直接在 builder 中构建更加有效。

比如下面的代码,直接在 builder 中构建子组件:

AnimatedBuilder(
animation: animation,
builder: (BuildContext context, Widget child) {
return Transform.rotate(
angle: animation.value,
child: FlutterLogo(size: 60,),
);
},
)

优化后的代码:

AnimatedBuilder(
animation: animation,
builder: (BuildContext context, Widget child) {
return Transform.rotate(
angle: animation.value,
child: child,
);
},
child: FlutterLogo(size: 60,),
)

谨慎的使用一些组件

部分组件一定要谨慎使用,因为这些组件包含一些昂贵的操作,比如 saveLayer() 方法。

调用saveLayer()会分配一个屏幕外缓冲区。 将内容绘制到屏幕外缓冲区中可能会触发渲染目标切换,这在较早的GPU中特别慢。

另外虽然下面这些组件比较消耗性能,但并不是禁止大家使用,而是谨慎使用,如果有替代方案,考虑使用替代方法。

尤其注意,如果这些组件频繁重建(比如动画的过程),要重点优化。

Clip 类组件

Clip 类组件是常用的裁剪类组件,比如:ClipOval、ClipPath、ClipRRect、ClipRect、CustomClipper。这些组件中都有 clipBehavior 属性,不同的值性能是不同的,

///  * [hardEdge], which is the fastest clipping, but with lower fidelity.
/// * [antiAlias], which is a little slower than [hardEdge], but with smoothed edges.
/// * [antiAliasWithSaveLayer], which is much slower than [antiAlias], and should
/// rarely be used.

越往下,速度越慢。

一些简单的圆角组件的设置可以使用 Container 实现:

Container(
height: 200,
width: 200,
decoration: BoxDecoration(
image: DecorationImage(
image: NetworkImage(
'https://flutter.github.io/assets-for-api-docs/assets/widgets/owl-2.jpg'),
fit: BoxFit.cover,
),
border: Border.all(
color: Colors.blue,
width: 2,
),
borderRadius: BorderRadius.circular(12),
),
)

Opacity

Opacity 组件的功能是使子组件透明。此类将其子级绘制到中间缓冲区中,然后将子级混合回到部分透明的场景中。

对于除0.0和1.0之外的不透明度值,此类相对昂贵,因为它需要将子级绘制到中间缓冲区中。 对于值0.0,根本不绘制子级。 对于值1.0,将立即绘制没有中间缓冲区的子对象。

如果仅仅是对单个 Image 或者 Color 增加透明度,直接使用比 Opacity 组件更快:

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

比使用 Opacity 组件更快:

Opacity(opacity: 0.5, child: Container(color: Colors.red))

如果对组件的透明度进行动画操作,建议使用 AnimatedOpacity

还有一些组件也要慎重使用,比如:

  • ShaderMask
  • ColorFilter
  • BackdropFilter

文中如果有不完善或者不正确的地方欢迎提出意见,后面如果优化的补充将会在我的博客中进行补充,地址:

http://laomengit.com/blog/20201227/improve_performance.html

参考链接:

https://flutter.dev/docs/perf/rendering/best-practices

https://api.flutter.dev/flutter/widgets/Opacity-class.html#transparent-image

https://api.flutter.dev/flutter/widgets/StatefulWidget-class.html#performance-considerations

交流

老孟Flutter博客(330个控件用法+实战入门系列文章):http://laomengit.com

添加微信或者公众号领取 《330个控件大全》和 《Flutter 实战》PDF。

欢迎加入Flutter交流群(微信:laomengit)、关注公众号【老孟Flutter】:

【老孟Flutter】如何提高Flutter应用程序的性能的更多相关文章

  1. Chrome 开发者工具的Timeline和Profiles提高Web应用程序的性能

    Chrome 开发者工具的Timeline和Profiles提高Web应用程序的性能 二.减少 HTTP 的请求数    当用户浏览页面时,如果我们在用户第一次访问时将一些信息一次性加载到客户端缓存, ...

  2. 使用Memcached提高.NET应用程序的性能

    在应用程序运行的过程中总会有一些经常需要访问并且变化不频繁的数据,如果每次获取这些数据都需要从数据库或者外部文件系统中去读取,性能肯定会受到影响,所以通常的做法就是将这部分数据缓存起来,只要数据没有发 ...

  3. 【转】使用Memcached提高.NET应用程序的性能

    在应用程序运行的过程中总会有一些经常需要访问并且变化不频繁的数据,如果每次获取这些数据都需要从数据库或者外部文件系统中去读取,性能肯定会受到影响,所以通常的做法就是将这部分数据缓存起来,只要数据没有发 ...

  4. 使用Chrome DevTools的Timeline和Profiles提高Web应用程序的性能

    来源: http://www.oschina.net/translate/performance-optimisation-with-timeline-profiles 我们都希望创建高性能的Web应 ...

  5. 使用Memcached提高.NET应用程序的性能(转)

    标签:分布式缓存 .NET Memcached Performance 性能 原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 .作者信息和本声明.否则将追究法律责任.http://zh ...

  6. 第十九篇:提高SOUI应用程序渲染性能的三种武器

    SOUI是一套100%开源的基于DirectUI的客户端开发框架. 基于DirectUI设计的UI虽然UI呈现的效果可以很炫,但是相对于传统的win32应用程序中每个控件一个窗口句柄的形式,渲染效率是 ...

  7. 【老孟Flutter】2021 年 Flutter 官方路线图

    老孟导读:这是官方公布的2021年路线图,向我们展示了2021年 Flutter 的主要工作及计划. 原文地址:https://github.com/flutter/flutter/wiki/Road ...

  8. 【老孟Flutter】Flutter 2.0 重磅更新

    老孟导读:昨天期待已久的 Flutter 2.0 终于发布了,Web 端终于提正了,春季期间我发布的一篇文章,其中的一个预测就是 Web 正式发布,已经实现了,还有一个预测是:2021年将是 Flut ...

  9. 【老孟Flutter】Flutter 2的新功能

    老孟导读:昨天期待已久的 Flutter 2.0 终于发布了, Flutter Web和Null安全性趋于稳定,Flutter桌面安全性逐渐转向Beta版! 原文链接:https://medium.c ...

随机推荐

  1. double类型和int类型的区别

    引例: double a=19*3.3; System.out.print(a); 结果为62.9999996,不是62.7:这里不单纯是因为给的是double类型 (1) 62.7 和 62.699 ...

  2. 快用Django REST framework写写API吧

    Django默认是前后端绑定的,提供了Template和Form,现在流行前后端分离项目,Python大佬坐不住了,于是便有了Django REST framework:https://github. ...

  3. 加快Linux上yum下载安装包的速度(以CentOS 7,安装gcc为例)

    今天在学习Linux的过程中,学到了关于包的安装问题:rpm包管理和yum在线管理两种方式:这里因为我在实验yum安装gcc出现了网速超级慢的问题,于是搜索解决方案,重新配置repo得以解决,记录整个 ...

  4. 【超详细】MakeDown(Typora)+PicGo+Gitee实现图床

    [超详细]MakeDown(Typora)+PicGo+Gitee实现图床 序言:我们有时在用makedown整理笔记时想把自己的笔记上传到博客园,可是发现在上传过程中makedown中的图片显示不出 ...

  5. Python炫技操作:五种Python 转义表示法

    1. 为什么要有转义? ASCII 表中一共有 128 个字符.这里面有我们非常熟悉的字母.数字.标点符号,这些都可以从我们的键盘中输出.除此之外,还有一些非常特殊的字符,这些字符,我通常很难用键盘上 ...

  6. Multipass使用教程

    一.Multipass介绍 Multipass是一种简单的虚拟机工具.它不仅使启用虚拟机变得快速简易,还使管理那些虚拟机变得异常简单,因此可以立即开始针对云.边缘.物联网或任何一种类型的技术进行开发. ...

  7. matplotlib学习日记(三)------简单统计图

    (一)函数bar()---------绘制柱状图 import matplotlib as mpl import matplotlib.pyplot as plt mpl.rcParams[" ...

  8. 本地H5模式写的APP体验可以比APP还好

    很多APP使用H5编写,但APP链接的是远程的url的模式,导致了APP的用户体验极差,因为当你使用远程的H5 url的时候,打开H5页面的速度由网络决定,而不是由手机性能决定,假如用户在没有网络的地 ...

  9. springcloud执行流程理解图

    执行流程图

  10. 对象、对象监视器、同步队列、执行线程关系(synchronized的实现细节或原理)

    synchronized在使用的时候底层细节你了解吗,相信很多同学对细节很少关注:比如竞争失败了的线程怎么安置,每个对象的监视器,线程执行synchronized时,其实是获取对象的监视器才能进入同步 ...