[TOC]
# 前言
Flutter 作为目前最火爆的移动端跨平台框架,能够帮助开发者通过一套代码库高效地构建多平台的精美应用,并支持移动、Web、桌面和嵌入式平台。对于 Android 来说,Flutter 能够创作媲美原生的高性能应用,但是,在较为复杂的 App 中,使用 Flutter 开发也很难避免产生各种各样的性能问题。在这篇文章中,我将和你一起全方位地深入探索 Flutter 性能优化的疆域。
# 一、检测手段
### 准备
以 profile 模式启动应用,如果是混合 Flutter 应用,在 flutter/packages/flutter\_tools/gradle/flutter.gradle 的 buildModeFor 方法中将 debug 模式改为 profile即可。
### 为什么要在分析模式下来调试应用性能?
分析模式在发布模式的基础之上,为分析工具提供了少量必要的应用追踪信息。
### 那,为什么要在发布模式的基础上来调试应用性能?
与调试代码可以在调试模式下检测 Bug 不同,性能问题需要在发布模式下使用真机进行检测。这是因为,相比发布模式而言,**调试模式增加了很多额外的检查(比如断言),这些检查可能会耗费很多资源,而更重要的是,调试模式使用 JIT 模式运行应用,代码执行效率较低。这就使得调试模式运行的应用,无法真实反映出它的性能问题**。
而另一方面,**模拟器使用的指令集为 x86,而真机使用的指令集是 ARM**。这两种方式的二进制代码执行行为完全不同,因此,模拟器与真机的性能差异较大,例如,针对一些 x86 指令集擅长的操作,模拟器会比真机快,而另一些操作则会比真机慢。这也同时意味着,你无法使用模拟器来评估真机才能出现的性能问题。
## 1、Flutter Inspector
Flutter Inspector有很多功能,但你应该把注意力花在更有用的功能学习上,例如:**“Select Widget Mode” 和 “Repaint Rainbow”**。
### Select Widget Mode
点击 “Select Widget Mode” 图标,可以在手机上查看当前页面的布局框架与容器类型。
![](https://img.kancloud.cn/f4/ec/f4ec0df4bb86885b7de8118a626343ae_402x140.png)
#### 作用
**快速查看陌生页面的布局实现方式**。
### Repaint Rainbow
点击 “Repaint Rainbow” 图标,它会 **为所有 RenderBox 绘制一层外框,并在它们重绘时会改变颜色**。
![](https://img.kancloud.cn/f5/1e/f51efa77c48f5bed93c997a14b55a730_234x132.png)
#### 作用
**帮你找到 App 中频繁重绘导致性能消耗过大的部分**。
例如:一个小动画可能会导致整个页面重绘,这个时候使用 RepaintBoundary Widget 包裹它,可以将重绘范围缩小至本身所占用的区域,这样就可以减少绘制消耗。
#### 使用场景
例如 **页面的进度条动画刷新时会导致整个布局频繁重绘**。
#### 缺点
**使用 RepaintBoundary Widget 会创建额外的绘制画布,这将会增加一定的内存消耗**。
## 2、性能图层
性能图层会在当前应用的最上层,以 Flutter 引擎自绘的方式展示 Raster 与 UI 线程的执行图表,而其中每一张图表都代表当前线程最近 300 帧的表现,如果 UI 产生了卡顿(跳帧),这些图表可以帮助你分析并找到原因。
**蓝色垂直的线条表示已执行的正常帧,绿色的线条代表的是当前帧,如果其中有一帧处理时间过长,就会导致界面卡顿,图表中就会展示出一个红色竖条**。
**如果红色竖条出现在 GPU 线程图表,意味着渲染的图形太复杂,导致无法快速渲染;而如果是出现在了 UI 线程图表,则表示 Dart 代码消耗了大量资源,需要优化代码的执行时间**。如下图所示:
![](https://img.kancloud.cn/5c/fc/5cfc1b7030f3b39670b76d2af6329a3b_1080x397.png)
## 3、Raster 线程问题定位
它定位的是 **渲染引擎底层渲染的异常**。
解决方案是 **把需要静态缓存的图像加入到 RepaintBoundary。而 RepaintBoundary 可以确定 Widget 树的重绘边界,如果图像足够复杂,Flutter 引擎会自动将其缓存,避免重复刷新。当然,因为缓存资源有限,如果引擎认为图像不够复杂,也可能会忽略 RepaintBoundary**。
## 4、UI 线程问题定位
### 问题场景
在视图构建时,在 build 方法中使用了一些复杂的运算,或是在主 Isolate 中进行了同步的 I/O 操作。
### 使用 Performance 进行检测
点击 Android Studio 底部工具栏中的 “Open DevTools” 按钮,然后在打开的 Dart DevTools 网页中将顶部的 tab 切换到 Performance。
与性能图层能够自动记录应用执行的情况不同,使用 Performance 来分析代码执行轨迹,你需要手动点击 “Record” 按钮去主动触发,在完成信息的抽样采集后,点击 “Stop” 按钮结束录制。这时,你就可以得到在这期间应用的执行情况了。
**使用 Performance 记录应用的执行情况,即 CPU 帧图,又被称为火焰图。火焰图是基于记录代码执行结果所产生的图片,用来展示 CPU 的调用栈,表示的是 CPU 的繁忙程度**。
其中:
* **y 轴**:表示调用栈,其每一层都是一个函数。**调用栈越深,火焰就越高,底部就是正在执行的函数,上方都是它的父函数**。
* **x 轴**:表示单位时间,一**个函数在 x 轴占据的宽度越宽,就表示它被采样到的次数越多,即执行时间越长**。
所以,我们要 **检测 CPU 耗时问题,皆可以查看火焰图底部的哪个函数占据的宽度最大。只要有 “平顶”,就表示该函数可能存在性能问题**。如下图所示:
![](https://img.kancloud.cn/9f/37/9f37795aed22ae47c089201bd2160fa2_1080x629.png)
一般的耗时问题,我们通常可以 **使用 Isolate(或 compute)将这些耗时的操作挪到并发主 Isolate 之外去完成**。
> dart 的单线程执行异步任务是怎么实现的?
网络调用的执行是由操作系统提供的另外的底层线程做的,而在 event queue 里只会放一个网络调用的最终执行结果(成功或失败)和响应执行结果的处理回调。
## 5、使用 checkerboardOffscreenLayers 检查多视图叠加的视图渲染
只要在 MaterialApp 的初始化方法中,将 checkerboardOffscreenLayers 开关设置为 true,分析工具就会自动帮你检测多视图叠加的情况。
这时,使用了 saveLayer 的 Widget 会自动显示为棋盘格式,并随着页面刷新而闪烁。
而 saveLayer 一般会通过一些功能性 Widget,在涉及需要剪切或半透明蒙层的场景中间接地使用。
## 6、使用 checkerboardRasterCacheImages 检查缓存的图像
它也是用来检测在界面重绘时频繁闪烁的图像(即没有静态缓存)。解决方案是把需要静态缓存的图像加入到 RepaintBoundary。
# 二、关键优化指标
## 1、页面异常率
页面异常率,即 页**面渲染过程中出现异常的概率**。
它度量的是页面维度下功能不可用的情况,其统计公式为:
> 页面异常率 = 异常发生次数 / 整体页面 PV 数。
### 统计异常发生次数
利用 Zone 与 FlutterError 这两个方法,然后在异常拦截的方法中,去累计异常的发生次数。
### 统计整体页面 PV 数
继承自 NavigatorObserver 的观察者,并在其 didPush 方法中,去累加页面的打开次数。
## 2、页面帧率
Flutter 在全局 Window 对象上提供了帧回调机制。我们可以在 Window 对象上注册 onReportTimings 方法,将最近绘制帧耗费的时间(即 FrameTiming),以回调的形式告诉我们。
有了每一帧的绘制时间后,我们就可以计算 FPS 了。
为了让 FPS 的计算更加平滑,我们需要保留最近 25 个 FrameTiming 用于求和计算。
由于帧的渲染是依靠 VSync 信号驱动的,如果帧绘制的时间没有超过 16.67 ms,我们也需要把它当成 16.67 ms 来算,因为绘制完成的帧必须要等到下一次 VSync 信号来了之后才能渲染。而如果帧绘制时间超过了 16.67 ms,则会占用后续 VSync 的信号周期,从而打乱后续的绘制次序,产生卡顿现象。
那么,页面帧率的统计公式就是:
> FPS = 60 \* 实际渲染的帧数 / 本来应该在这个时间内渲染完成的帧数。
首先,定义一个容量为 25 的列表,用于存储最近的帧绘制耗时 FrameTiming。
然后,在 FPS 的计算函数中,你再将列表中每帧绘制时间与 VSync 周期 frameInterval 进行比较,得出本来应该绘制的帧数。
最后,两者相除就得到了 FPS 指标。
## 3、页面加载时长
> 页面加载时长 = 页面可见的时间 - 页面创建的时间(包括网络加载时长)
### 统计页面可见的时间
**WidgetsBinding 提供了单次 Frame 回调的 addPostFrameCallback 方法,它会在当前 Frame 绘制完成之后进行回调,并且只会回调一次。一旦监听到 Frame 绘制完成回调后,我们就可以确认页面已经被渲染出来了**,因此我们可以借助这个方法去获取页面的渲染完成时间 endTime。
### 统计页面创建的时间
获取页面创建的时间比较容易,我们只需要在页面的初始化函数 initState() 里记录页面的创建时间 startTime。
最后,再将这两个时间做减法,你就能得到页面的加载时长。
需要注意的是,**正常的页面加载时长一般都不应该超过2秒。如果超过了,则意味着有严重的性能问题**。
# 三、布局加载优化
> Flutter 为什么要使用声明书 UI 的编写方式?
为了减轻开发人员的负担,无需编写如何在不同的 UI 状态之间进行切换的代码,Flutter 使用了声明式的 UI 编写方式,而不是 Android 和 iOS 中的命令式编写方式。
这样的话,**当用户界面发生变化时,Flutter 不会修改旧的 Widget 实例,而是会构造新的 Widget 实例**。
Fluuter 框架使用 RenderObjects 管理传统 UI 对象的职责(比如维护布局的状态)。RenderObjects 在帧之间保持不变, Flutter 的轻量级 Widget 通知框架在状态之间修改 RenderObjects, 而 Flutter Framework 则负责处理其余部分。
## 1、常规优化
常规优化即针对 build() 进行优化,build() 方法中的性能问题一般有两种:**耗时操作和 Widget 堆叠**。
### 1)、在 build() 方法中执行了耗时操作
我们应该尽量避免在 build() 中执行耗时操作,因为 build() 会被频繁地调用,尤其是当 Widget 重建的时候。
此外,我们不要在代码中进行阻塞式操作,可以将文件读取、数据库操作、网络请求等通过 Future 来转换成异步方式来完成。
最后,对于 CPU 计算频繁的操作,例如图片压缩,可以使用 isolate 来充分利用多核心 CPU。
**isolate 作为 Flutter 中的多线程实现方式,之所以被称之为 isolate(隔离),是因为每一个 isolate 都有一份单独的内存**。
**Flutter 会运行一个事件循环,它会从事件队列中取得最旧的事件,处理它,然后再返回下一个事件进行处理,依此类推,直到事件队列清空为止。每当动作中断时,线程就会等待下一个事件**。
实质上,不仅仅是 isolate,所有的高级 API 都能够应用于异步编程,例如 Futures、Streams、async 和 await,它们全部都是构建在这个简单的事件循环之上。
而,async 和 await 实际上只是使用 futures 和 streams 的替代语法,它将代码编写形式从异步变为同步,主要用来帮助你编写更清晰、简洁的代码。
此外,async 和 await 也能使用 try on catch finally 来进行异常处理,这能够帮助你处理一些数据解析方面的异常。
### 2)、build() 方法中堆砌了大量的 Widget
这将会导致三个问题:
* 1、**代码可读性差**:画界面时需要一个 Widget 嵌套一个 Widget,但如果 Widget 嵌套太深,就会导致代码的可读性变差,也不利于后期的维护和扩展。
* 2、**复用难**:由于所有的代码都在一个 build(),会导致无法将公共的 UI 代码复用到其它的页面或模块。
* 3、**影响性能**:我们在 State 上调用 setState() 时,所有 build() 中的 Widget 都将被重建,因此 build() 中返回的 Widget 树越大,那么需要重建的 Widget 就越多,也就会对性能越不利。
所以,你需要 **控制 build 方法耗时,将 Widget 拆小,避免直接返回一个巨大的 Widget,这样 Widget 会享有更细粒度的重建和复用**。
### 3)、使用 Widget 而不是函数
如果一个函数可以做同样的事情,Flutter 就不会有 StatelessWidget ,使用 StatelessWidget 的最大好处在于:能尽量避免不必要的重建。总的来说,它的优势有:
* 1)、**允许性能优化:const 构造函数,更细粒度的重建等等**。
* 2)、**确保在两个不同的布局之间切换时,能够正确地处理资源(因为函数可能重用某些先前的状态)**。
* 3)、**确保热重载正常工作,使用函数可能会破坏热重载**。
* 4)、**在 flutter 自带的 Widget 显示工具中能看到 Widget 的状态和参数**。
* 5)、**发生错误时,有更清晰的提示:此时,Flutter 框架将为你提供当前构建的 Widget 名称,更容易排查问题**。
* 6)、**可以定义 key 和方便使用 context 的 API**。
### 4)、尽可能地使用 const
如果某一个实例已经用 const 定义好了,那么其它地方再次使用 const 定义时,则会直接从常量池里取,这样便能够节省 RAM。
### 5)、尽可能地使用 const 构造器
当构建你自己的 Widget 或者使用 Flutter 的 Widget 时,这将会帮助 Flutter 仅仅去 rebuild 那些应当被更新的 Widget。
因此,你应该尽量多用 const 组件,这样即使父组件更新了,子组件也不会重新进行 rebuild 操作。特别是针对一些长期不修改的组件,例如通用报错组件和通用 loading 组件等。
### 6)、使用 nil 去替代 Container() 和 SizedBox()
首先,你需要明白 **nil 仅仅是一个基础的 Widget 元素 ,它的构建成本几乎没有**。
在某些情况下,如果你不想显示任何内容,且不能返回 null 的时候,你可能会返回类似 const SizedBox/Container 的 Widget,但是 SizedBox 会创建 RenderObject,而渲染树中的 RenderObject 会带来多余的生命周期控制和额外的计算消耗,即便你没有给 SizedBox 指定任何的参数。
下面,是我平时使用 nil 的一套方式:
~~~
// BEST
text != null ? Text(text) : nil
or
if (text != null) Text(text)
text != null ? Text(text) : const Container()/SizedBox()
~~~
### 7)、列表优化
在构建大型网格或列表的时候,我们要尽量避免使用 ListView(children: \[\],) 或 GridView(children: \[\],)。
因为,在这种场景下,不管列表内容是否可见,会导致列表中所有的数据都会被一次性绘制出来,这种用法类似于 Android 的 ScrollView。
如果我们列表数据比较大的时候,建议使用 ListView 和 GridView 的 builder 方法,它们只会绘制可见的列表内容,类似于 Android 的 RecyclerView。
其实,本质上,**就是对列表采用了懒加载而不是直接一次性创建所有的子 Widget,这样视图的初始化时间就减少了**。
### 8)、针对于长列表,记得在 ListView 中使用 itemExtent。
有时候当我们有一个很长的列表,想要用滚动条来大跳时,使用 itemExtent 就很重要了,**它会帮助 Flutter 去计算 ListView 的滚动位置而不是计算每一个 Widget 的高度,与此同时,它能够使滚动动画有更好的性能**。
### 9)、减少可折叠 ListView 的构建时间
**针对于可折叠的 ListView,未展开状态时,设置其 itemCount 为 0,这样 item 只会在展开状态下才进行构建,以减少页面第一次的打开构建时间**。
### 10)、尽量不要为 Widget 设置半透明效果
考虑用图片的形式代替,这样被遮挡的部分 Widget 区域就不需要绘制了。
除此之外,还有网络请求预加载优化、抽取文本 Theme 等常规的优化方式就不赘述了。
## 2、深入优化
### 1)、优化光栅线程
所有的 Flutter 应用至少都会运行在两个并行的线程上:**UI 线程和 Raster 线程**。
\*\*UI 线程是你构建 Widgets 和运行应用逻辑的地方。\*\***Raster 线程是 Flutter 用来栅格化你的应用的。它从 UI 线程获取指令并将它们转换为可以发送到图形卡的内容**。
**在光栅线程中,会获取图片的字节,调整图像的大小,应用透明度、混合模式、模糊等等,直到产生最后的图形像素。然后,光栅线程会将其发送到图形卡,继而发送到屏幕上显示**。
使用 Flutter DevTools-Performance 进行检测,步骤如下:
* 1、在 Performance Overlay 中,查看光栅线程和 UI 线程哪个负载过重。
* 2、在 Timeline Events 中,找到那些耗费时间最长的事件,例如常见的 SkCanvas::Flush,它负责解决所有待处理的 GPU 操作。
* 3、找到对应的代码区域,通过删除 Widgets 或方法的方式来看对性能的影响。
### 2)、用 key 加速 Flutter 的性能优化光栅线程
一个 element 是由 Widget 内部创建的,它的主要目的是,**知道对应的 Widget 在 Widget 树中所处的位置。但是元素的创建是非常昂贵的,通过 Keys(ValueKeys 和 GlobalKeys),我们可以去重复使用它们**。
> GlobalKey 与 ValueKey 的区别?
**GlobalKey 是全局使用的 key,在跨小部件的场景时,你就可以使用它去刷新其它小部件。但,它是很昂贵的,如果你不需要访问 BuildContext、Element 和 State,应该尽量使用 LocalKey**。
而 ValueKey 和 ObjectKey、UniqueKey 一样都归属于局部使用的 LocalKey,无法跨容器使用,ValueKey 比较的是 Widget 的值,而 ObjectKey 比较的是对象的 key,UniqueKey 则每次都会生成一个不同的值。
#### 元素的生命周期
* **Mount**:挂载,当元素第一次被添加到树上的时候调用。
* **Active**:当需要激活之前失活的元素时被调用。
* **Update**:用新数据去更新 RenderObject。
* **Deactive**:当元素从 Widget 树中被移除或移动时被调用。如果一个元素在同一帧期间被移动了且它有 GlobalKey,那么它仍然能够被激活。
* **UnMount**:卸载,如果一个元素在一帧期间没有被激活,它将会被卸载,并且再也不会被复用。
#### 优化方式
**为了去改善性能,你需要去尽可能让 Widget 使用 Activie 和 Update 操作,并且尽量避免让 Widget触发 UnMount 和 Mount**。而使用 GlobayKeys 和 ValueKey 则能做到这一点:
~~~
/// 1、给 MaterialApp 指定 GlobalKeys
MaterialApp(key: global, home: child,);
/// 2、通过把 ValueKey 分配到正在被卸载的根 Widget,你就能够
/// 减少 Widget 的平均构建时间。
Widget build(BuildContext context) {
return Column(
children: [
value
? const SizedBox(key: ValueKey('SizedBox'))
: const Placeholder(key: ValueKey('Placeholder')),
GestureDetector(
key: ValueKey('GestureDetector'),
onTap: () {
setState(() {
value = !value;
});
},
child: Container(
width: 100,
height: 100,
color: Colors.red,
),
),
!value
? const SizedBox(key: ValueKey('SizedBox'))
: const Placeholder(key: ValueKey('Placeholder')),
],
);
}
~~~
> 如何知道哪些 Widget 会被 Update,哪些 Widget会被 UnMount?
只有 build 直接 return 的那个根 Widget 会自动更新,其它都有可能被 UnMount,因此都需要给其分配 ValueKey。
> 为什么没有给 Container 分配 ValueKey?
因为 Container 是 GestureDetector 的一个子 Widget,所以当给 GestureDetector 使用 ValueKey 去实现复用更新时,Container 也能被自动更新。
#### 优化效果
优化前:
![图片](data:image/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQImWNgYGBgAAAABQABh6FO1AAAAABJRU5ErkJggg==)
优化后:
![图片](data:image/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQImWNgYGBgAAAABQABh6FO1AAAAABJRU5ErkJggg==)
可以看到,平均构建时间 **由 5.5ms 减少到 1.6ms**,优化效果还是很明显的。
#### 优势
大幅度减少 Widget的平均构建时间。
#### 缺点
* **过多使用 ValueKey 会让你的代码变得更冗余**。
* **如果你的根 Widget 是 MaterialApp 时,则需要使用 GlobalKey,但当你去重复使用 GlobalKey 时可能会导致一些错误,所以一定要避免滥用 Key**。
注意📢:在大部分场景下,Flutter 的性能都是足够的,不需要这么细致的优化,只有当产生了视觉上的问题,例如卡顿时才需要去分析优化。
# 四、启动速度优化
## 1、Flutter 引擎预加载
使用它可以达到页面秒开的一个效果,具体实现为:
在 HIFlutterCacheManager 类中定义一个 preLoad 方法,**使用 Looper.myQueue().addIdleHandler 添加一个 idelHandler,当 CPU 空闲时会回调 queueIdle 方法,在这个方法里,你就可以去初始化 FlutterEngine,并把它缓存到集合中**。
预加载完成之后,你就可以通过 HIFlutterCacheManager 类的 getCachedFlutterEngine 方法从集合中获取到缓存好的引擎。
## 2、Dart VM 预热
对于 Native + Flutter 的混合场景,如果不想使用引擎预加载的方式,那么要提升 Flutter 的启动速度也可以通 过Dart VM 预热来完成,这种方式会提升一定的 Flutter 引擎加载速度,但整体对启动速度的提升没有预加载引擎提升的那么多。
![图片](data:image/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQImWNgYGBgAAAABQABh6FO1AAAAABJRU5ErkJggg==)
**无论是引擎预加载还是 Dart VM 预热都是有一定的内存成本的,如果 App 内存压力不大,并且预判用户接下来会访问 Flutter 业务,那么使用这个优化就能带来很好的价值;反之,则可能造成资源浪费,意义不大**。
# 五、内存优化
## 1、const 实例化
### 优势
**const 对象只会创建一个编译时的常量值。在代码被加载进 Dart Vm 时,在编译时会存储在一个特殊的查询表里,由于 flutter 采用了 AoT 编译,const + values 的方式会提供一些小的性能优势**。例如:const Color() 仅仅只分配一次内存给当前实例。
### 应用场景
Color()、GlobayKey() 等等。
## 2、识别出消耗多余内存的图片
Flutter Inspector:**点击 “Invert Oversized Images”,它会识别出那些解码大小超过展示大小的图片,并且系统会将其倒置,这些你就能更容易在 App 页面中找到它**。
![图片](https://mmbiz.qpic.cn/mmbiz_png/PjzmrzN77aCOvWZZvDka4Zz7ZBSzVFA6FxBZumXDSfTNRkKUhZeCz92jqkAQ5rwibEOGeg5YAsDibaKEPpCUT4WA/640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1)
针对这些图片,你可以指定 cacheWidth 和 cacheHeight 为展示大小,这样可以让 flutter 引擎以指定大小解析图片,减少内存消耗。
## 3、针对 ListView item 中有 image 的情况来优化内存
ListView 不能够杀死那些在屏幕可视范围之外的那些 item,如果 item 使用了高分辨率的图片,那么它将会消耗非常多的内存。
换言之,ListView 在默认情况下会在整个滑动/不滑动的过程中让子 Widget 保持活动状态,这一点是通过 AutomaticKeepAlive 来保证,在默认情况下,每个子 Widget 都会被这个 Widget 包裹,以使被包裹的子 Widget 保持活跃。
其次,如果用户向后滚动,则不会再次重新绘制子 Widget,这一点是通过 RepaintBoundaries 来保证,在默认情况下,每个子 Widget 都会被这个 Widget 包裹,它会让被包裹的子 Widget 仅仅绘制一次,以此获得更高的性能。
但,这样的问题在于,如果加载大量的图片,则会消耗大量的内存,最终可能使 App 崩溃。
### 解决方案
**通过将这两个选项置为 false 来禁用它们,这样不可见的子元素就会被自动处理和 GC**。
~~~
ListView.builder(
...
addAutomaticKeepAlives: false (true by default)
addRepaintBoundaries: false (true by default)
);
~~~
**由于重新绘制子元素和管理状态等操作会占用更多的 CPU 和 GPU 资源,但是它能够解决你 App 的内存问题,并且会得到一个高性能的视图列表**。
# 六、包体积优化
## 1、图片优化
对图片压缩或使用在线的网络图片。
## 2、移除冗余的二三库
随着业务的增加,项目中会引入越来越多的二三方库,其中有不少是功能重复的,甚至是已经不再使用的。移除不再使用的和将相同功能的库进行合并可以进一步减少包体积。
## 3、启用代码缩减和资源缩减
打开 minifyEnabled 和 shrinkResources,构建出来的 release 包会减少 10% 左右的大小,甚至更多。
## 4、构建单 ABI 架构的包
目前手机市场上,x86 / x86\_64/armeabi/mips / mips6 的占有量很少,arm64-v8a 作为最新一代架构,是目前的主流,而 armeabi-v7a 只存在少部分的老旧手机中。
所以,**为了进一步优化包大小,你可以构建出单一架构的安装包,在 Flutter 中可以通过以下方式来构建出单一架构的安装包**:
~~~
cd <flutter应用的android目录>
flutter build apk --split-per-abi
~~~
如果想进一步压缩包体积可将 so 进行动态下发,将 so 放在远端进行动态加载,不仅能进一步减少包体积也可以实现代码的热修复和动态加载。
# 七、总结
在本篇文章中,我主要从以下 六个方面 讲解了 Flutter 性能优化相关的知识:
* 1)、**检测手段**:Flutter Inspector、性能图层、Raster 和 UI 线程问题的定位 使用 checkerboardOffscreenLayers 检查多视图叠加的视图渲染 、使用 checkerboardRasterCacheImages 检查缓存的图像。
* 2)、**关键优化指标**:包括页面异常率、页面帧率、页面加载时长。
* 3)、**布局加载优化**:十大常规优化、优化光栅线程、用 key 加速 Flutter 的性能。
* 4)、**启动速度优化**:引擎预加载和 Dart VM 预热。
* 5)、**内存优化**:const 实例化、识别出消耗多余内存的图片、针对 ListView item 中有 image 的情况来优化内存。
* 6)、**包体积优化**:图片优化、移除冗余的二三库、启用代码缩减和资源缩减、构建单 ABI 架构的包。
在近一年实践 Flutter 的过程中,越发发现一个人真正应该具备的核心能力应该是你的思考能力。
思考能力,包括 **结构化思考/系统性思考/迁移思考/层级思考/逆向思考/多元思考** 等,使用这些思考能力分析
# 参考资料
[深入探索 Flutter 性能优化](https://mp.weixin.qq.com/s/pUNlOXGfYo4taFteOg0SxA)
- Android
- 四大组件
- Activity
- Fragment
- Service
- 序列化
- Handler
- Hander介绍
- MessageQueue详细
- 启动流程
- 系统启动流程
- 应用启动流程
- Activity启动流程
- View
- view绘制
- view事件传递
- choreographer
- LayoutInflater
- UI渲染概念
- Binder
- Binder原理
- Binder最大数据
- Binder小结
- Android组件
- ListView原理
- RecyclerView原理
- SharePreferences
- AsyncTask
- Sqlite
- SQLCipher加密
- 迁移与修复
- Sqlite内核
- Sqlite优化v2
- sqlite索引
- sqlite之wal
- sqlite之锁机制
- 网络
- 基础
- TCP
- HTTP
- HTTP1.1
- HTTP2.0
- HTTPS
- HTTP3.0
- HTTP进化图
- HTTP小结
- 实践
- 网络优化
- Json
- ProtoBuffer
- 断点续传
- 性能
- 卡顿
- 卡顿监控
- ANR
- ANR监控
- 内存
- 内存问题与优化
- 图片内存优化
- 线下内存监控
- 线上内存监控
- 启动优化
- 死锁监控
- 崩溃监控
- 包体积优化
- UI渲染优化
- UI常规优化
- I/O监控
- 电量监控
- 第三方框架
- 网络框架
- Volley
- Okhttp
- 网络框架n问
- OkHttp原理N问
- 设计模式
- EventBus
- Rxjava
- 图片
- ImageWoker
- Gilde的优化
- APT
- 依赖注入
- APT
- ARouter
- ButterKnife
- MMKV
- Jetpack
- 协程
- MVI
- Startup
- DataBinder
- 黑科技
- hook
- 运行期Java-hook技术
- 编译期hook
- ASM
- Transform增量编译
- 运行期Native-hook技术
- 热修复
- 插件化
- AAB
- Shadow
- 虚拟机
- 其他
- UI自动化
- JavaParser
- Android Line
- 编译
- 疑难杂症
- Android11滑动异常
- 方案
- 工业化
- 模块化
- 隐私合规
- 动态化
- 项目管理
- 业务启动优化
- 业务架构设计
- 性能优化case
- 性能优化-排查思路
- 性能优化-现有方案
- 登录
- 搜索
- C++
- NDK入门
- 跨平台
- H5
- Flutter
- Flutter 性能优化
- 数据跨平台