likes
comments
collection
share

Flutter Engine 源码调试(2024.10 含 iOS、Android 启动阶段)Flutter Engin

作者站长头像
站长
· 阅读数 43

一、背景

本文相对其他Flutter Engine源码调试文章有两个亮点:

  1. 包含 iOS 和 Android 两个平台。
  2. 包含如何调试 Embedder 和 Engine 的启动阶段。

本文结构分为源码获取、构建、调试三部分。实践环境为:

  1. 构建机器:MacOS M1。
  2. 构建目标平台:arm64 架构的 iOS、Android(本文均用模拟器调试)。
  3. 构建源码:Flutter 3.24.3 ,包含 Framework、 Embedder、Engine。
  4. IDE:Android Studio、Xcode。

二、Flutter 源码获取

2.1 源码在哪

本文调试的源码都存在哪?先说明一个Flutter App的代码具体分布(Flutter 架构详情可参考官网 The-Engine-architecture):

Flutter Engine 源码调试(2024.10 含 iOS、Android 启动阶段)Flutter Engin

从上图中可知,整体代码架构可分5部分,而代码存储位置为3块:

  1. app repo: 存Dart AppRunner这两层代码,由App开发者自行管理。
  2. flutter/flutter: 存 Flutter Framework 的代码。
  3. flutter/engine: 存 Flutter Engine、Flutter Embedder 的代码。

本文调试的代码则是第2和第3点。

扩展:官方有简单说明为什么 Engine 要独立于 Framework 存储 ( why we have a separate engine repo ),我理解是为了实现《UNIX编程艺术》中介绍的正交性,即是系统设计中要保持模块之间的独立性,最终实现代码的可维护性和可重用性,降低系统的复杂性。

2.2 Flutter Framework 源码获取

flutter/flutter该仓库用于存放 Flutter SDK,包含 Flutter Framework 源码,要能用于开发 Flutter App,除了 clone 源码外还要设置环境变量、安装构建目标平台的编译工具,所以获取 Flutter Framework 源码和安装 SDK 直接参考官网的 Install Flutter 即可。这只提几个初学者容易忽略的点:

  1. Flutter Framework 源码是随 Flutter 工程一起构建的。这点和闭源的 iOS UIKit 不一样,它是构建好的 Framework ,无法修改源码或断点调试内部实现。
  2. 对于 Flutter App 开发者,无需管理 Flutter Engine、Flutter Embedder,flutter 相关工具会按Framework 对应的版本,拉取构建好的 Engine 和 Embedder。(在 Flutter Framework ./bin/engine.version 中记录 Engine 仓库对应 commit )

2.3 Flutter Engine、Flutter Embedder 源码获取

flutter/engine该仓库存 Flutter Engine、Flutter Embedder 的代码,但仅clone 源码还不能用于调试,因为它们还依赖其他三方库,依赖管理需使用 Chromium 的 depot_tools。完整流程可分以下几个步骤(参考官网 Setting up the Engine development environment) :

2.3.1 终端工具配置VPN

在终端命令行工具设置http代理,如代理端口号是7898

export http_proxy=http://127.0.0.1:7898
export https_proxy=http://127.0.0.1:7898

注意:

  1. 使用结束后可用 unset http_proxy unset https_proxy 取消代理设置。
  2. 终端命令是ping不通Google的,可用curl https://www.google.com 检测代理生效。
2.3.2 下载和配置depot_tools

通过git clone下载 depot_tools git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git

将 depot_tools 配置至环境变量,在~/.zshrc~/.bash_profile 中添加 export PATH = "所在目录/depot_tools":$PATH,保存文件后执行 source ~/.zshrc 或 source .bash_profile

在终端执行 depot_tools 测试是否成功,若成功可见类似信息:

% gclient                                        
Usage: gclient.py  [options]  
Meta checkout dependency manager for Git.

Commands are:
  xxxx

Options:
  xxxx
2.3.3 下载 Engine 源码
mkdir engine
cd engine
fetch flutter

终端执行以上命令,会在 engine 目录中创建 .gclient文件并从https://github.com/flutter/engine.git 下载源码及其对应依赖。

注意:在构建调试前还需切换到当前 Flutter Framework 对应的commit(从Framework 的./bin/engine.version文件可知),切换 commit 还需执行 gclient sync 同步新的依赖。例如Flutter Framework 3.24.3 对应commit是 36335019a8eab588c3c2ea783c618d90505be233,需在 engine 目录下执行以下命令:

cd ./src/flutter
git checkout 36335019a8eab588c3c2ea783c618d90505be233
gclient sync --with_branch_heads --with_tags

三、Flutter 源码构建

2.2节提到 Flutter Framework 源码是随 Flutter 工程一起构建的,不再特殊说明,本章主要记录 Flutter Engine、Flutter Embedder 的构建(可参考官网的 Compiling the engine)。

简单来说,Flutter 复用 Chromium 工程的 gn 和 ninja 工具链完成 Engine 和 Embedder 的构建,先用 gn 生成构建元文件,再用 ninja 执行实际构建。

注意:使用 gn 生成构建元文件时,需用 Flutter Engine 源码下的 /src/flutter/tools/gn ,而不是全局环境变量的。

3.1 Android

cd xxx/engine/src
./flutter/tools/gn --android --android-cpu arm64 --unoptimized --no-stripped
./flutter/tools/gn --unoptimized --mac-cpu arm64
ninja -C out/android_debug_unopt_arm64 & ninja -C out/host_debug_unopt_arm64

注意:gn --android --android-cpu arm64 --unoptimized --no-stripped 一定要加上 --no-stripped 参数,否则4.2.1节中的构建产物 app-debug.apk 没有可调试的符号文件,只能断点调试 Flutter Embedder,在 Flutter Engine 中无法命中断点。

3.2 iOS

cd xxx/engine/src
./flutter/tools/gn --ios --simulator --simulator-cpu=arm64 --unoptimized 
./flutter/tools/gn --unoptimized --mac-cpu arm64
ninja -C out/ios_debug_sim_unopt_arm64 & ninja -C out/host_debug_unopt_arm64

四、Flutter 源码调试

4.1 Framework 源码调试

这里以Android Studio为例,只要在 Preferences | Languages & Frameworks | FlutterFlutter SDK path 设置为2.2节获取的源码位置,就能在 Android Studio 断点调试 Flutter Framework 代码。

例如在 binding.dartrunApp方法中可断点调试(把鼠标放在顶部所属文件tab上,会显示文件路径确实为本地源码)。

4.2 Embedder 和 Engine 源码调试

4.2.1 Android

参考官网的 Debugging native engine code on Android with Android Studio,使用Android Studio 调试 Flutter Embedder 和 Flutter Engine 主要有五步:

  1. 用本地构建的 Flutter Engine 运行需要调试的 App。对应3.1 中构建产物的命令为:

    flutter run -d emulator-5554 --debug --local-engine-src-path /xx/engine/src --local-engine=android_debug_unopt_arm64 --local-engine-host=host_debug_unopt_arm64
    
  2. 打开构建产物的调试工程。打开 File > Profile or Debug APK 后选择调试 App 目录下的 build/app/outputs/apk/debug/app-debug.apk

  3. 调试工程导入 Flutter Engine 源码。将本地engine/src/flutter 文件copy至app-debug 目录下后等 Android Studio 完成 indexing 即可。例如:Flutter Engine 源码调试(2024.10 含 iOS、Android 启动阶段)Flutter Engin

  4. 配置同时支持 Java、C++ 调试。打开 Run > Attach Debugger to Android Process, "Use Android Debugger Settings from" 选择 [Use default settings], 在选择 Dual(Java + Native)

  5. Attach 运行中的 Flutter App 并断点调试。在Attach Debugger to Android Process 配置好后点OK,验证断点是否生效。

    1. Flutter Embedder 命中断点效果:Flutter Engine 源码调试(2024.10 含 iOS、Android 启动阶段)Flutter Engin
    2. Flutter Engine 命中断点效果:Flutter Engine 源码调试(2024.10 含 iOS、Android 启动阶段)Flutter Engin

再和大家分享下两个调试小技巧:

1. 如何调试启动流程

妙用Debug.waitForDebugger()。在 Android Runner App 的 FlutterActivity 子类 onCreate 方法里,加上Debug.waitForDebugger()阻塞当前线程,直到 lldb attach App 。例如:

protected void onCreate(Bundle savedInstanceState) {  
    if (!Debug.isDebuggerConnected()) {  
        Debug.waitForDebugger();  
    }
    super.onCreate(savedInstanceState);
}
2. 如何用Android Studio运行本地Engine

gradle.properties指定本地引擎参数。上面调试第1步用的是 flutter tool ,若想 Android Studio 中用本地引擎直接运行和调试 Flutter App, 可修改测试 Flutter App 的 android/gradle.properties 文件,例子中对应的参数为:

local-engine-repo=xxx/flutter_tool_local_engine_repo.YbVWZJ  
local-engine-out=xxx/engine/src/out/android_debug_unopt_arm64  
local-engine-host-out=xxx/engine/src/out/host_debug_unopt_arm64  
local-engine-build-mode=debug  
target-platform=android-arm64

需要注意的是local-engine-repo 参数,要指定本地的 maven 仓库。从 Flutter SDK 的 flutter.groovy可知运行 flutter run --local-engine-src-path --local-engine --local-engine-host 会构建临时的本地 maven ,我们直接复用该 maven 仓库即可,在 flutter run时加上--verbose参数,会把临时 maven 打印出来 'Local Maven repo: ${localEngineRepo.path}'(建议把临时maven copy出来免得被清空,打印 maven 逻辑参考 gradle.dart )。

4.2.2 iOS

参考官网的 Debugging iOS builds with Xcode,使用 Xcode 调试 Flutter Embedder 和 Flutter Engine 主要有三步:

  1. 配置 Runner Xcode 工程指定本地 Flutter Engine。执行配置命令后能看到xxx/ios/Flutter/Generated.xcconfig 已配置本地引擎 。对应3.2 中构建产物的命令为:

    flutter build ios --local-engine-src-path xxx/engine/src --local-engine=ios_debug_sim_unopt_arm64 --local-engine-host=host_debug_unopt_arm64 --config-only
    
  2. 将 Flutter Engine xcodeproj 导入 Runner App。将 engine/src/out/ios_debug_sim_unopt_arm64/flutter_engine.xcodeproj 拖到 Runner 的 Xcode 工程即可,如图:Flutter Engine 源码调试(2024.10 含 iOS、Android 启动阶段)Flutter Engin

  3. Xcode 运行 Flutter App 并断点调试。

    1. Flutter Embedder 命中断点效果:Flutter Engine 源码调试(2024.10 含 iOS、Android 启动阶段)Flutter Engin
    2. Flutter Engine 命中断点效果: Flutter Engine 源码调试(2024.10 含 iOS、Android 启动阶段)Flutter Engin

本小节的iOS 的调试配置比较简单,而且调试启动流程不需要像 Android 加 Debug.waitForDebugger()

转载自:https://juejin.cn/post/7423583639080812596
评论
请登录