Flutter引擎
前言
作为开发iOS
的一名程序员,大环境不好了,学点Flutter
吧。今天我们从背景知识,Flutter
引擎编译,Flutter
引擎验证调试,Flutter
引擎应用等方面来研究下Flutter
引擎
背景知识
Flutter
是一个跨平台的UI
工具集,它的设计初衷,就是允许在各种操作系统上复用
同样的代码。
在开发中,Flutter
应用会在一个 VM
(程序虚拟机)中运行,从而可以在保留状态且无需重新编译的情况下,热重载相关的更新
架构层
Flutter
引擎 毫无疑问是 Flutter
的核心,它主要使用 C++
编写,并提供了 Flutter
应用所需的原语
。当需要绘制新一帧的内容时,引擎将负责对需要合成的场景进行栅格化
。它提供了 Flutter
核心 API
的底层实现,包括图形(在 iOS
和 Android
上通过 Impeller
,在其他平台上通过 Skia
)、文本布局、文件及网络 IO
、辅助功能支持、插件架构和 Dart
运行环境及编译环境的工具链。
引擎将底层 C++
代码包装成 Dart
代码,通过 dart:ui
暴露给 Flutter
框架层。该库暴露了最底层的原语,包括用于驱动输入、图形、和文本渲染的子系统的类。
通常,开发者可以通过 Flutter
框架层 与 Flutter
交互,该框架提供了以 Dart
语言编写的现代响应式框架。它包括由一系列层组成的一组丰富的平台,布局和基础库。从下层到上层,依次有:
-
基础的
foundational
类及一些基层之上的构建块服务,如animation
、painting
和gestures
,它们可以提供上层常用的抽象。 -
渲染层 用于提供操作布局的抽象。有了渲染层,你可以构建一棵可渲染对象的树。在你动态更新这些对象时,渲染树也会自动根据你的变更来更新布局。
-
widget
层 是一种组合的抽象。每一个渲染层中的渲染对象,都在widgets
层中有一个对应的类。此外,widgets
层让你可以自由组合你需要复用的各种类。响应式编程模型就在该层级中被引入。 -
Material
和Cupertino
库提供了全面的widgets
层的原语组合,这套组合分别实现了Material
和iOS
设计规范。
应用剖析
下图为你展示了一个通过 flutter create
命令创建的应用的结构概览。该图展示了引擎在架构中的定位,突出展示了 API
的操作边界,并且标识出了每一个组成部分
DartApp
我们写的dart
代码,一般在/lib
目录下
- 将 widget 合成预期的 UI。
- 实现对应的业务。
- 由应用开发者进行管理。
Framwork
位于Flutter SDK
的flutter/packages/flutter/lib/src
路径下
- 提供了上层的 API 封装,用于构建高质量的应用(例如 widget、触摸检测、手势竞技、无障碍和文字输入)。
- 将应用的 widget 树构建至一个 Scene 中。
engin
Flutter SDK
的引擎在flutter/bin/cache/artifacts/engine
路径下
- 将已经合成的 Scene 进行栅格化。
- 对 Flutter 的核心 API 进行了底层封装(例如图形图像、文本布局和 Dart 的运行时)
- 将其功能通过 dart:ui API 暴露给框架。
- 使用 嵌入层 API 与平台进行整合。
引擎的源码对应的路径在engin/src/flutter/shell/common
下
embedder
embedder
对应的带源码路径在engin/src/flutter/shell/platform/embedder
下
- 协调底层操作系统的服务,例如渲染层、无障碍和输入。
- 管理事件循环体系。
- 将 特定平台的 API 暴露给应用集成嵌入层。
Runner
对应各个平台的宿主APP
- 将嵌入层暴露的平台 API 合成为目标平台可以运行的应用包。
- 部分内容由
flutter create
生成,由应用开发者进行管理。
编译引擎
配置depot_tools
Chromium
使用了depot_tools
来管理代码,获取 depot_tools
源码前,需开启 VPN
服务,科学上网
- Git拉取代码
git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git
- 配置环境变量
export PATH="$PATH:$HOME/depot_tools"
新建目录
mkdir engine
路径上最好不要带中文,不然后续下载编译运行引擎代码可能会有奇奇怪怪的报错
配置gclient文件
cd engine
touch .gclient
文件内容如下:
solutions = [ {
"managed": False, "
name": "src/flutter",
"url": "git@github.com:flutter/engine.git@360ca05311c8fafe73333ca2c58dfecf9b701d40",
"custom_deps": {},
"deps_file": "DEPS",
"safesync_url": "", }, ]
其中360ca05311c8fafe73333ca2c58dfecf9b701d40
是commitId,查找方式如下:
flutter --version
查看flutter
版本,笔者这边是3.10.6
- 打开github.com/flutter/eng…
- 搜索找到
flutter-3.10-candidate.6
的分支 - 复制最新的commitId
过程中需要注意的是:引擎产物跟flutter
的sdk
版本是一一对应的,所以我们选择stable
的版本,代码基本不会变,这样相对稳定点,不用经常编译
同步代码
gclient sync
这个操作是个耗时操作,需要科学上网,过程中会下载Flutter
所有的依赖,执行完可以做其他事情,慢慢等着就好了,大概有10来G
的文件
SDK升级
我们知道SDK版本跟引擎是一一对应的,升级了SDK怎么升级引擎呢
- 我们先找到SDK下的
flutter/bin/internal/engine.version
,里面有对应的commitId
- 到
.gclient
⽂件修改对用的commitId
- 然后到
src/flutter
下
git pull
git reset --hard commitID
- 回到
engine
⽬录,也就是.gclient
⽂件所在的⽬录
$gclient sync --with_branch_heads --with_tags --verbose
使用GN工具生成
GN
这个东西是一个生成Ninja
构建文件的元构建
系统,找到它的路径如下:
使用如下命令生成Ninja
构建文件
#构建iOS设备使⽤的引擎
#真机debug版本
./gn --ios --unoptimized
#真机release版本(⽇常开发使⽤,如果我们要⾃定义引擎)
./gn --ios --unoptimized --runtime-mode=release
#模拟器版本
./gn --ios --simulator --unoptimized
#主机端(Mac)构建
./gn --unoptimized
得到的4个Xcode工程如下:
过程中笔者出现以下报错:
大家有兴趣可以参看 github.com/flutter/flu… 配置goma
环境
笔者这里直接使用了--no-goma
的flag
,也顺利构建了
使用Ninja编译工程
ninja -C host_debug_unopt && ninja -C ios_debug_sim_unopt && ninja -C ios_debug_unopt && ninja -C ios_release_unopt
这也是个耗时操作,笔者这里编译了4个,也可以根据自己的需要只编译自己需要的部分工程
编译完的四个引擎也要进30个G
了
编译后的产物:
至此flutter
引擎编译部分到此就结束了
可以使用lipo -info xxx
来查看产物的架构信息
使用引擎
配置工程
命令行新建flutter
工程, 打开iOS
工程,找到Generated.xcconfig
文件,最后加上如下两行:
FLUTTER_ENGINE=你存放引擎代码的路径/engine/src
#使⽤的引擎对应版本(这⾥是iOS-debug模式下-真机的版本)
LOCAL_ENGINE=ios_debug_unopt
验证引擎代码是否生效
打开引擎代码工程,找到engine.cc
文件
修改Engine::RunStatus Engine::Run(RunConfiguration configuration)
这个函数
加入一些自定义的代码
运行iOS
工程,我们奇怪的发现控制台并没有打印我们自定义的文案,这是怎么回事呢?
我们加下断点发现函数是能进来的
原来呀引擎代码修改也是要重新编译的,我们在跑下ninja
编译命令
这次只要编译16个文件
,所以速度比较快的,编译完代码再次运行iOS工程
这次终于打印了我们自定义的代码,这就验证了我们编译的引擎生效了!
调试引擎代码
我们在上面验证的过程中其实已经用到了断点的功能,没有断点断住的小伙伴看这里怎么调试代码
找到对应平台编译的产物下对应的flutter_engine_xcodeproj
工程拖入我们自己的iOS工程中
拖进去后
接着就可以随意断点
了
调试引擎代码就到此,要是还没调试成功的小伙伴检查下Generated.xcconfig
这个文件里配置的路径对不对,然后最后路径上不要有中文
(可能会有问题)。另外如果是Mac
工程的话需要配置的文件是Flutter-Generated.xcconfig
实际用途
调试学习
第一个用途当然是断点调试源码
了,不要太爽了,当然源码太多,一般学到什么功能再进行深入调试比较好
魔改引擎代码
Flutter
源码是支持动态化
的,可以通过魔改Flutter
引擎实现动态化。当然dart
本身是支持放射
的,但是flutter
禁用了dart
的放射
功能,不然也会被封杀吧。魔改引擎代码其实成本还蛮大的,普通公司慎用!
解决具体问题,比如内存问题
时间线回到22年6月
前,来看下这个issue:iOS指针压缩造成的OOM
当时的解决方案可以等官方更新SDK
,不过如果用户反馈比较多来不及等更新,只能改gn代码
:
找到gn
位置
做类似这样的改动
总结
- Flutter应用可以剖析为DartApp,Framwork,Engin,Embedder,Runner,前三层是平台无关的,后两层是平台相关的
- 编译引擎:
- 配置depot_tools
- 配置gclient文件,注意commitId
- gclient sync同步代码
- SDK升级修改对应commitId,重新sync
- 使用GN工具生成构建Ninja构建文件
- 使用Ninja编译工程
- 验证引擎:
- 修改代码注意重新编译,检查引擎路径
- 调试引擎代码:
- 拖进xcode工程断点调试
- 实际用途:
- 调试学习
- 动态化
- 解决具体问题,比如内存问题
转载自:https://juejin.cn/post/7267941441061224448