Flutter 引擎编译、运行与调试
介绍 Flutter 引擎编译、运行与调试的操作步骤。
编译
操作步骤
- 安装
depot_tools
并添加到环境变量。gclient
来自 depot_tools 工具 - fork flutter/engine (注意配置 ssh 访问)
- 创建空的
engine
目录并在目录中创建.gclient
配置文件 - 在
engine
目录中执行gclient sync
(它会git clone
必要的项目及其依赖)
1 | git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git |
.gitclient
配置如下:
1 | solutions = [ |
- 切换源码。编译前的一个重要操作是将源码切换到 本地 Flutter SDK 的 engine version (一个 commit id) 对应的提交点,避免可能出现的报错
1 | # 查看本地 Flutter SDK 引擎版本, 这个文件中是包含对应的 commit id |
尤其注意这里的 ninja -C out/host_debug_unopt
命令。官方文档提到它是必要的:
Note that if you use particular android or ios engine build, you will need to have corresponding host build available next to it: if you use android_debug_unopt, you should have built host_debug_unopt, android_profile -> host_profile, etc.
这个命令的编译结果包含 dart-sdk
,使用自己构建的 Flutter 引擎编译 App 时会调用 dart-sdk
中相关工具。
编译完成后的目录如下:
常见编译问题
编译 Flutter 其实并不复杂,运气好的话不会遇到任何问题,运气不好的话会遇到一些错误。不过出错通常是因为环境问题。
如果遇到错误,不妨先检查以下几项:
- 确认当前使用的是 Python 2.7 -
flutter/tools/gn
依赖的是 Python 2。如果当前环境中使用 Python 3 会出现各种诡异错误 - 确认已经将
depot_tools
工具添加到环境变量 -depot_tools
工具包含几个用于编译 Flutter 引擎的命令gclient
和ninja
。如果找不到这些命令,也会编译失败 - 切换引擎源码 - Flutter 引擎源码和本地 Flutter SDK 不匹配时可能会编译失败,编译前应确认已经将 Flutter 引擎源码切换到对应的提交点
Python 版本问题
这里简单记录一下我编译 Flutter 引擎时遇到的 Python 版本问题及解决方法。如果你没有遇到 Python 版本问题,可以直接忽略这一节。
我的 MacBook 系统内置了 Python 2.7,安装的第三方软件 Anaconda 自带的 Python 3.7。命令行下 python
默认的是 Anaconda 的 Python 3.7,不做任何处理时调用 flutter/tools/gn
会报 Python 语法错误。
推荐使用 virtualenv 来为 Flutter 引擎源码工程创建虚拟的 Python 2.7 环境,激活该环境后再来编译 Flutter 源码,可以有效避免各种 Python 版本折腾。操作如下:
1 | # 切换到 Flutter 引擎源码目录 |
缺省头文件
在云主机(Centos 7)上编译 Flutter 引擎源码时,glfw 库报错如下:
1 | ../../third_party/glfw/src/x11_platform.h:39:10: fatal error: 'X11/Xcursor/Xcursor.h' file not found |
glfw 提到解决办法如下:
For example, on Ubuntu and other distributions based on Debian GNU/Linux, you need to install the xorg-dev package, which pulls in all X.org header packages.
安装相关的库即可,方式如下(你可能要根据实际情况调整):
1 | yum install libX11-devel |
运行
操作步骤
命令行中使用自定义引擎:
1 | # 创建一个 Flutter 工程 |
IDE 中使用自定义引擎:
1 | # 创建一个 Flutter 工程 |
在 gradle.properties 文件中添加 localEngineOut 属性,配置如下:
1 | localEngineOut=<engine_dir>/out/android_debug_unopt_arm64 |
注意:
- 应当指定的本地引擎应当跟 Android 系统架构匹配,比如 armeabi-v8a 机器上使用
android_debug_unopt_arm64
- 尤其注意某些项目通过自行将 Flutter SDK
libflutter.so
拷贝到代码库的方式来集成 Flutter,这会导致上述方式失效,实际运行时并不会加载指定的本地引擎。解决方法是将拷贝对应的目录下(如android_debug_unopt_arm64
)的libflutter.so
覆盖代码库中已有有libflutter.so
即可
常见运行错误
最常见的问题是找不到指定的引擎导致无法运行 Flutter App。原因通常包括:
- 引擎文件路径写错
- 架构不匹配。以我手头的测试机华为 Nova 2 为例,它要求使用 arm64 类型的引擎,而我编译时没有注意到这一点,选择的是 arm 类型,最后发现引擎架构不匹配
- 缺少
host
产物。错误提示如下图
我自己遇到另外一个比较奇特的错误,通常应该不会遇到,但这里也记录下供参考。
事情经过是这样的:我的 MacBook 硬盘只有 256G,目前空间已经偏紧张,考虑编译 Flutter 引擎占 CPU 影响本地机器性能外加占用太多硬盘空间,所以我突发奇想。
- 首先在个人的 Linux 云主机上编译 Flutter 引擎(果然比本地快很多)
- 然后将 Linux 云主机的硬盘挂在 MacBook 当本地文件使用
- 最后使用远程机器上的 Flutter 引擎运行 App:
1 | # 挂载远程机器目录 |
最后出错,错误信息如下:
无法执行 dart 命令!Mac 系统当然无法执行 Linux 平台的二进制文件。
调试
断点调试 Flutter 引擎来一步步观察引擎代码如何运行,是学习 Flutter 引擎代码的一个好办法。在介绍如何调试 Flutter 引擎前我们先来看看 Flutter 开发中可能遇到哪些调试场景:
- 调试 Flutter App Dart 代码
- 调试 Flutter SDK Dart 代码
- 调试 Flutter 引擎 Java 代码
- 调试 Flutter 引擎 C++ 代码
第一种场景非常简单,只要在 VS Code 中给 Flutter App 中的 Dart 代码打上断点即可进行调试。
第二种场景也比较简单,在 VS Code 中配置 Dart & Flutter 插件,允许调试第三方库和 Flutter SDK Dart 代码即可在相关源码中设置断点进行调试
调试 Java 代码
再来看第三种场景,调试 Flutter 引擎中的 Java 代码。主要是参考以下资料(建议动手操作一下):
步骤如下:
- 第一步,将
engine/src/flutter/shell/platform/android
工程(称之为Flutter 引擎工程)导入到 Android Studio。注意一定是这个目录!另外,确认该工程的 Android SDK 和 JDK 版本正确 (当前分别是 29 和 8) - 第二步,使用自定义 Flutter 引擎运行 Flutter App(称之为Flutter App 工程),具体见上文描述
- 第三步,Flutter 引擎工程 中给源码设置断点并启动 Debugger 连接到已启动的 Flutter App 进程
各步骤的截图如下:
第一步,Flutter 引擎工程导入到 Android
第二步,Android Studio 中打开Flutter App 工程 并启动 Flutter App
1 | # 创建 Flutter App |
第三步,在 Flutter 引擎工程 将 Debugger 连接到已启动 Flutter App
有时候,我们想在更早的地方设置断点调试,比如在 libflutter.so 被加载前加断点调试,如何实现?
技巧在于第二步启动 Flutter App 时选择一个 合适的时机(通常是在断点之前) 调用 Debug.waitForDebugger 方法让应用启动后进入等待 Debugger 的状态。
示例:在 Application.onCreate()
中调用 Debug.waitForDebugger()
,应用一直处于等待状态,直到有 Debugger 连接上来才继续执行。
调试 C++ 代码
最后来看怎样调试 Flutter 引擎 C++ 代码。主要参考资料是:
简单来说,是使用 lldb 来调试 Flutter 引擎 C++ 代码。可以使用不同的方式来完成 lldb 调试,但实际操作起都比较麻烦。
个人理解主要麻烦之处在于,一是如何将 lldb_server
推送到开发设备并运行起来,二是如何从开发机正确地启动 lldb_client
。
从这些麻烦的方法中,我选择了一个相对简单的动手实践了一下并加以总结。步骤概括如下:
步骤 | 操作 | 作用 | 备注 |
---|---|---|---|
第一步 | 编译 Flutter 引擎 | 生成自定义引擎用于调试 | |
第二步 | 导入 Flutter 引擎源码到 VS Code | 支持 Flutter 引擎源码跳转及断点跟踪 | 后文称VS Code Flutter 引擎工程 |
第三步 | 安装 VS Code 插件 C/C++ for Visual Studio Code 和 CodeLLDB | 在 VS Code 中集成 lldb 调试功能 | |
第四步 | 安装 lldb 和 flutter_lldb | 1、lldb 用于调试 C++ 代码,2、flutter_lldb 用于简化 lldb 的使用 | |
第五步 | 运行 Flutter App | 加载和运行自定义引擎 | flutter run 或 Android Studio 中运行均可 |
第六步 | 执行 flutter_lldb 命令 |
1、生成调试配置参数,2、在调试设备上启动 lldb_server |
|
第七步 | VS Code Flutter 引擎工程 中配置 lldb 调试配置参数 | ||
第八步 | VS Code Flutter 引擎工程 中设置断点,F5 启动调试 |
为了便于理解和动手操作操作,这里给出一些截图。
第一步,编译 Flutter 引擎。注意确认已生成 out/host_debug_unopt
第二步,导入 Flutter 引擎源码到 VS Code
- 先将 [engine/src/out/compile_commands.json](JSON Compilation Database Format Specification) 拷贝到
engine/src/flutter/
- VS Code 中打开
engine/src/flutter/
第三步,安装 VS Code 插件,集成 lldb 调试功能
第四步,安装 lldb 和 flutter_lldb。
注意:
- lldb 安装在 Android SDK 中。如 Android SDK 已有 lldb,则可略过这一步
- 有一个坑,目前 Android Studio 内置 lldb,但却并不安装在 Android SDK 中,所以外部工具无法方便地调用 AS 内置 lldb,而 Android SDK Manager 软件列表中目前不再提供 lldb 安装,所以只能手工下载和安装 lldb
第五步,运行 Flutter App
第六步,执行 flutter_lldb
命令
flutter_lldb
输出的配置参数类似如下这样:
1 | { |
注意:
flutter_lldb
脚本基于 Python 2.7,使用前请确认当前 Python 环境flutter_lldb
脚本与调试设备通信,使用前请确认已连接调试设备且已启动应用
第七步,在 VS Code 中配置 lldb 命令调试参数。直接将上一步中 flutter_lldb
输出的配置参数拷贝到 launch.json
中即可
第八步,VS Flutter 引擎工程 中设置断点,F5 启动调试
一切正常的话,Debugger 将成功连接应用进程,进入调试状态,如下图:
Debug Console 输出显示已经成功连接到指定的进程
千万不要被上述繁琐的步骤吓到了。实际上,第一步到第四步只是准备阶段(只需准备一次),第五步到第八步才是真正的操作步骤(调试时需反复多次操作)。用一张图来总结下第五步到第八步:
操作几次之后,你会发现实际过程更简单。其实并不用真的的每次都要重新配置 launch.json
,只不过是修改下 pid 而已。
UPDATE:使用小米手机能正常调试 Flutter 引擎,但华为手机 Nova 2 上可以成功到 Attached to process
却无法进入调试状态。使用 NDK Sample 中的 HelloJNI 华为 Nova 2 验证,发现也不能正常调试,所以推测是华为手机问题。
(2020-07-24 更新:以下是 iOS 上调试 Flutter 引擎的步骤)
- 将 Flutte Engine 工程的
products.xcodeproj
拖进需要调试的 Flutter Demo 工程目录 Genrated.xcconfig
中添加如下配置
1 | FLUTTER_ROOT=${FlutterSDK 路径} |
之后即可单步调试。
总结
- 简单介绍如何编译 Flutter 引擎,以及常见问题
- 介绍如何从命令行和 IDE 运行自定义 Flutter 引擎,以及常见问题
- 简单介绍如何调试 Flutter App 及 Flutter SDK 中的 Dart 代码
- 详细介绍如何调试 Flutter 引擎中的 Java 代码和 C++ 代码