1. 启动速度优化
Flutter启动流程:
1). 一个Native进程只有一个DartVM
2). 第一个FlutterEngine初始化时,会创建并初始化DartVM
3). 一个DartVM可以有多个FlutterEngine,每个FlutterEngine都运行在自己的Isolate中,他们的内存数据不共享,需要通过Isolate事先设置的port(顶级函数)通讯。
2. 启动优化检测工具
2.1 Macrobenchmark
2.2 代码类型工具 , 无侵入性
使用App Startup
App Startup库提供了一种在应用启动时初始化组件的简单而高效的方法。库开发者和应用开发者都可以使用应用启动来简化启动序列,并明确设置初始化顺序。
通过应用启动,您可以定义共用单个 Content Provider 的组件初始化程序,而无需为需要初始化的每个组件定义单独的 Content Provider。这可以显著缩短应用启动时间。
implementation "androidx.startup:startup-runtime:1.1.1"
class TimberInitializer : Initializer<String> {
override fun create(context: Context): String {
Timber.plant(Timber.DebugTree())
return "TimberInit"
}
override fun dependencies(): List<Class<out Initializer<*>>> {
return emptyList()
}
}
// 调用
AppInitializer.getInstance(context)
.initializeComponent(ExampleLoggerInitializer::class.java)
3. 启动优化方案
3.1 Flutter 引擎预加载
使用场景:
Native和Flutter混合开发,通过FlutterFragment加载Flutter页面,但Flutter页面第一次加载时非常缓慢,可以通过Flutter预加载的方式来减少第一次加载的耗时。
使用它可以达到页面秒开的一个效果,具体实现为:
在 HIFlutterCacheManager 类中定义一个 preLoad 方法,使用 Looper.myQueue().addIdleHandler 添加一个 idelHandler,当 CPU 空闲时会回调 queueIdle 方法,在这个方法里,你就可以去初始化 FlutterEngine,并把它缓存到集合中。
预加载完成之后,你就可以通过 HIFlutterCacheManager 类的 getCachedFlutterEngine 方法从集合中获取到缓存好的引擎。
import android.content.Context;
import android.os.Looper;
import android.os.MessageQueue;
import io.flutter.embedding.engine.FlutterEngine;
import io.flutter.embedding.engine.FlutterEngineCache;
import io.flutter.embedding.engine.dart.DartExecutor;
import io.flutter.embedding.engine.loader.FlutterLoader;
import io.flutter.plugins.GeneratedPluginRegistrant;
public class FlutterHelper {
private FlutterHelper() {
}
//FlutterEngine缓存的key
public static final String FLUTTER_ENGINE = "flutter_engine";
//flutter初始化成功的消息
public static final String FLUTTER_ENGINE_INIT_FINISH = "flutter_engine_init_finish";
private static volatile FlutterHelper instance;
public static FlutterHelper getInstance() {
if (instance == null) {
synchronized (FlutterHelper.class) {
if (instance == null) {
instance = new FlutterHelper();
}
}
}
return instance;
}
public void preloadFlutterEngine(Context context) {
Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
@Override
public boolean queueIdle() {
initFlutterEngine(context);
return true;
}
});
}
public synchronized FlutterEngine initFlutterEngine(Context context){
if (!FlutterEngineCache.getInstance().contains(FLUTTER_ENGINE)) {
FlutterEngine engine = new FlutterEngine(context.getApplicationContext());
System.out.println("flutterEngine:"+engine);
GeneratedPluginRegistrant.registerWith(engine);
FlutterBridge.init(engine);
engine.getDartExecutor().executeDartEntrypoint(DartExecutor.DartEntrypoint.createDefault());
FlutterEngineCache.getInstance().put(FLUTTER_ENGINE, engine);
return engine;
} else {
return getFlutterEngine();
}
}
public FlutterEngine getFlutterEngine(){
if (isInitFinish()) {
return FlutterEngineCache.getInstance().get(FLUTTER_ENGINE);
} else {
try {
throw new Exception("请先初始化 FlutterEngine!");
} catch (Exception e) {
e.printStackTrace();
}
}
return null;
}
public boolean isInitFinish() {
return FlutterEngineCache.getInstance().get(FLUTTER_ENGINE) != null;
}
}
3.2 Dart VM 预热
对于 Native + Flutter 的混合场景,如果不想使用引擎预加载的方式,那么要提升 Flutter 的启动速度也可以通 过Dart VM 预热来完成,这种方式会提升一定的 Flutter 引擎加载速度,但整体对启动速度的提升没有预加载引擎提升的那么多。
无论是引擎预加载还是 Dart VM 预热都是有一定的内存成本的,如果 App 内存压力不大,并且预判用户接下来会访问 Flutter 业务,那么使用这个优化就能带来很好的价值;反之,则可能造成资源浪费,意义不大。
3.3 避免启动初始化过多耗时任务(分块加载初始化)
eg:网络初始化可以放在闪屏页面,和用户强关联的模块、sdk可以放在登录之后操作。
3.4 页面启动耗时避免initState方法里面做。
任务放在状态管理机制里面处理,页面启动"友好交互"原则(加载动画、或先展示旧数据等新数据处理完成之后再刷新)
3.5 页面build里面有耗时操作
我们应该尽量避免在 build() 中执行耗时操作,因为 build() 会被频繁地调用,尤其是当 Widget 重建的时候。
此外,我们不要在代码中进行阻塞式操作,可以将文件读取、数据库操作、网络请求等通过 Future 来转换成异步方式来完成。
最后,对于 CPU 计算频繁的操作,例如图片压缩,可以使用 isolate 来充分利用多核心 CPU。
4. 渲染优化
我们可知 Flutter 的主要渲染流程:在初次渲染时,我们会根据我们自己的业务代码,分别构建 Widget、 Element 以及 RenderObject 三棵树,其次对 RenderObjective Tree 的每个节点进行遍历 ,再对发生改变的节点处进行标脏处理,执行 paint 操作,形成一个 Layer Tree,最后把形成好的 Layer Tree 发送给 GPU 线程,GPU 线程 在接收到 Layer Tree 之后,将 Layer Tree 转成为 GPU 的可执行指令
我们可知 Flutter 在 UI 线程中渲染主要涉及到 build、 layout 以及 paint 阶段
4.1 build优化方案:
在我们业务开发中,我们遵循以下方法,可以有效的控制 build 的耗时:
- 在创建 build 时,我们得让 build 十分纯粹,不能有其他的副作用。 (降低我们开始遍历的节点)
2.尽量避免写出嵌套很深的 Widget,应该把他们一个一个独立出来,这样可以有效地降低我们开始遍历的节点。(提前结束树的遍历)
4.2 layout 阶段的性能优化
layout 的过程主要是为了计算出节点真正所占的大小。在建立 layout tree 的过程中,首先父节点会给出一个宽高大小的限制,然后子节点再来决定自己的大小。在 Layout 中存在一个 Relayout boundary 的概念,它可以产生一个边界,确保在边界内的布局发生改变时,不会让边界外的部分也重新计算,这样也可以在某些特定情况下提高我们应用的性能。除此之外,在我们书写 Widget 的时候,如果能够给出 Widget 宽高的话,尽量给出来,因为在布局中,宽度的计算也会占用一定的时间。比如在使用 ListView 这样的滑动组件时,我们应该给出滑块的高度,即 itemExtend 的值,这样在滑动的时候,UI 线程不会花费大量的时间在计算高度上。
class ListViewShow extends StatelessWidget {
const ListViewShow({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
return ListView(
itemExtent: 30, // 指定 children 每一个 child 的高度
children: <Widget>[
Container(
child: Text('ListView 1'),
),
Container(
child: Text('ListView 2'),
),
Container(
child: Text('ListView 3'),
),
],
);
}
}
4.3 paint 阶段的性能优化
在 RenderObject 标脏后,paint 会对已经标脏的 RenderObject 图层重新进行绘制。这里和 Layout 相似,存在一个 Repaint boundary 的概念,它的原理和 layout 里面的 Relayout boundary 的基本相似,区别是它在 paint 的时候产生一个边界,防止页面大范围重新绘制。如果页面是频繁更新的页面,例如包含定时器的页面,在使用倒计时这样的控件时,我们可以在最小控件范围外包一层 RepaintBoundary 来与周围图层进行隔离。同 build 阶段一样,我们可以在代码里面加入debugProfilePaintsEnabled = true来在 timeline 里面观看 paint 阶段有哪些不必要的图层发生了更新
import 'package:flutter/material.dart';
import 'dart:async';
class HoursTitle extends StatefulWidget {
@override
HoursTitleState createState() => HoursTitleState();
}
class HoursTitleState extends State<HoursTitle> {
static Timer timer;
int minutes = DateTime.now().minute;
int seconds = DateTime.now().second;
Duration duration = Duration(seconds: 1);
void _startTimer() {
timer?.cancel();
timer = Timer.periodic(duration, (timer) {
if (seconds == 0) {
if (minutes == 0) {
minutes = 59;
seconds = 59;
} else {
minutes--;
seconds = 59;
}
} else {
seconds--;
}
setState(() {
minutes = minutes;
seconds = seconds;
});
});
}
@override
void initState() {
super.initState();
if (!mounted) {
return;
}
_startTimer();
}
@override
void dispose() {
super.dispose();
timer?.cancel();
}
Widget build(BuildContext context) {
// 通过RepaintBoundary增加一个绘制边界
return Container(
child: Row(
children: [
RepaintBoundary(
child: Container(
width: 200,
height: 30,
child: Text.rich(
TextSpan(
text: '距本小时结束',
children: [
TextSpan(
text: '$minutes : $seconds',
),
],
),
),
),
),
Text('123'),
Text('465'),
],
),
);
}
}
最后
在优化Flutter应用性能时,除了上述方法外,开发者还可以使用Appuploader这样的iOS开发助手工具来协助进行性能分析和优化。Appuploader提供了便捷的打包和上传功能,可以帮助开发者快速验证优化效果。
为了能够方便大家快速学习Flutter,这里整理了Flutter学习路线图以及《Flutter Dart 语言编程入门到精通》&《Flutter实战:第二版》帮助大家配置相关环境,学习Flutter的基本语法以及最后的项目实际利用。
学习路线:
Dart语言是Flutter的开发语言,所以我们需要掌握Dart语言的基础知识。
《Flutter Dart 语言编程入门到精通》
第一章 Dart语言基础
- 环境准备
- 基础语法
第二章 Dart 异步编程
- Dart的事件循环
- 调度任务
- 延时任务
- Future详解
- async和await
- lsolate
第三章 异步之 Stream 详解
- 什么是Stream
- 单订阅流
- 广播流
- Stream Transformer
- 总结
第四章 Dart标准输入输出流
- 文件操作
第五章 Dart 网络编程
- TCP服务端
- TCP客户端
- UDP服务端
- UDP客户端
- HTTP服务器与请求
- WebSocket
第六章 Flutter 爬虫与服务端
- Dart爬虫开发
- Dart服务端
- 创建Flutter项目演示
- 总结
第七章 Dart 的服务端开发
- 注册登录实现
第八章 Dart 调用C语言混合编程
- 环境准备
- 测试Dart ffi接口
- 总结
第九章 LuaDardo中Dart与Lua的相互调用
- Lua C API
- 创建运行时
- Dart调Lua
- Lua调Dart
掌握了Dart语言之后,咱们就可以通过实战来掌握Flutter的知识点。
《Flutter实战:第二版》
第一章:起步
- 1.1 移动开发技术简介
- 1.2 初始Flutter
- 1.3 搭建Flutter开发环境
- 1.4 Dart语言简介
第二章:第一个Flutter应用
- 2.1 计数器应用实例
- 2.2 Widget简介
- 2.3 状态管理
- 2.4路由管理
- 2.5包管理
- 2.6 资源管理
- 2.7 调试Flutter应用
- 2.8 Flutter异常捕获
第三章:基础组件
- 3.1 文本及样式
- 3.2 按钮
- 3.3 图片及ICON
- 3.4 单选开关和复选框
- 3.5 输入框及表单
- 3.6 进度指示器
第四章:布局类组件
- 4.1 布局类组件简介
- 4.2 布局原理与约束(constraints)
- 4.3 线性布局(Row和Column)
- 4.4 弹性布局(Flex)
- ...
第五章:容器类组件
- 5.1 填充(Padding)
- 5.2 装饰容器(DecoratedBox)
- 5.3 变换(Transform)
- 5.4 容器组件(Container)
- 5.5 剪裁(Clip)
- 5.6 空间适配(FittedBox)
- 5.7 页面骨架(Scaffold)
第六章:可滚动组件
- 6.1 可滚动组件简介
- 6.2 SingleChildScrollView
- 6.3 ListView
- 6.4 滚动监听及控制
- ...
第七章:功能型组件
- 7.1 导航返回拦截(WillPopScope)
- 7.2 数据共享(InheritedWidget)
- 7.3 跨组件状态共享
- 7.4 颜色和主题
- 7.5 按需rebuild(ValueListenableBuilder)
- 7.6 异步UI更新(FutureBuilder、StreamBuilder)
- 7.7 对话框详解
第八章:事件处理与通知
- 8.1 原始指针事件处理
- 8.2 手势识别
- 8.3 Flutter事件机制
- 8.4 手势原理与手势冲突
- 8.5 事件总线
- 8.6 通知 Notification
第九章:动画
- 9.1 Flutter动画简介
- 9.2 动画基本结构及状态监听
- 9.3 自定义路由切换动画
- 9.4 Hero动画
- 9.5 交织动画
- 9.6 动画切换组件(AnimatedSwitcher)
- 9.7 动画过渡组件
第十章:自定义组件
- 10.1 自定义组件方法简介
- 10.2 组合现有组件
- 10.3 组合实例:TurnBox
- 10.4 CustomPaint 与 Canvas
- 10.5 自绘实例:圆形背景渐变进度条
- 10.6 自绘组件:CustomCheckbox
- 10.7 自绘组件: DoneWidget
- 10.8 水印实例: 文本绘制与离屏渲染
第十一章:文件操作与网络请求
- 11.1 文件操作
- 11.2 通过HttpClient发起HTTP请求
- 11.3 Http请求库-dio
- 11.4 实例:Http分块下载
- ...
第十二章:Flutter扩展
- 12.1 包和插件
- 12.2 Flutter Web
第十三章:国际化
- 13.1 让App支持多语言
- 13.2 实现Localizations
- 13.3 使用Intl包
- 13.4 国际化常见问题
第十四章:Flutter核心原理
- 14.1 Flutter UI 框架(Framework)
- 14.2 Element、BuildContext和RenderObject
- 14.3 Flutter启动流程和渲染管线
- 14.4 布局(Layout)过程
- 14.5 绘制(一)绘制原理及Layer
- ...
第十五章:一个完整的Flutter应用
- 15.1 Github客户端示例
- 15.2 Flutter APP代码结构
- 15.3 Model类定义
- 15.4 全局变量及共享状态
- 15.5 网络请求封装
- 15.6 APP入口及主页
- 15.7 登录页
- 15.8 多语言和多主题