我从来不相信什么懒洋洋的自由,我向往的自由是通过勤奋和努力实现的更广阔的人生,那样的自由才是珍贵的、有价值的;我相信一万小时定律,我从来不相信天上掉馅饼的灵感和坐等的成就。做一个自由又自律的人,靠势必实现的决心认真地活着。
前言
xcode_backend.sh
位于$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh
,是Flutter编译iOS产物的一个关键部分,本篇文章用于分析该脚本。
为何要分析?
当我们创建完毕Flutter Module,并且通过官方的方式引入了Flutter框架后,我们会在Target->Build Phases->Run Script
中可以看到这么两句话:
"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh" build
"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh" embed
从字面上看,第一步是build
编译,第二个是embed
嵌入Framework到宿主APP。
- Embed 的意思是嵌入,但是这个嵌入并不是嵌入 app 可执行文件,而是嵌入 app 的 bundle 文件。
因此,如果我们要了解Flutter混合编译的来龙去脉,就需要分析下xcode_backend到底做了什么东西。
主函数入口
# 主函数入口
if [[ $# == 0 ]]; then # 如果不带参数则直接执行BuildApp函数
# Backwards-compatibility: if no args are provided, build.
BuildApp
else # 否则执行case语句
case $1 in
"build")
BuildApp ;; # 编译
"thin")
ThinAppFrameworks ;; # 只合并需要的架构
"embed")
EmbedFlutterFrameworks ;; # Embed
esac
fi
因此,Xcode_backend包含了三部分的实现,即build、thin、embed。
踩坑的本机环境
注意对比下我的环境和你的环境是否一样,有些问题在Flutter的新版本中已经被修复了。
➜ app git:(master) ✗ flutter --version
Flutter 1.9.1+hotfix.6 • channel stable • https://github.com/flutter/flutter.git
Framework • revision cc949a8e8b (3 weeks ago) • 2019-09-27 15:04:59 -0700
Engine • revision b863200c37
Tools • Dart 2.5.0
文章总结
xcode_backend.sh
的主要作用:
- iOS工程直接依赖Flutter工程,每次编译的时候都会执行
Target->Build Phases->Run Script
的xcode_backend.sh脚本。
在以下指令中:
"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh" build
"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh" embed
- xcode_backend.sh做的事情:
- 导入Flutter引擎的对应模式版本(
BuildApp
所做的事情) - 编译Dart代码为App.framework(
BuildApp
所做的事情) - 编译flutter_assets,并内嵌到App.framework(
BuildApp
所做的事情) - 复制资源,并签名(
EmbedFlutterFrameworks
所做的事情)
- 导入Flutter引擎的对应模式版本(
Build
主要包含几个部分的工作:
-
检查路径和资源是否存在
- 目录不存在就创建
- Flutter 引擎不存在则报错
-
检查输入的变量是否符合
-
拷贝Flutter引擎到工程目录下,
${SOURCE_ROOT}/Flutter
或者${project_path}/.ios/Flutter
。 -
编译App.framework
- Debug模式:生成App.framework,并生成dSYM
- Release/Profile模式:生成App.framework
-
编译资源包
编译App.framework
为了方便大家阅读,特意将几个重要的命令提取出来:
Release/Profile
# 执行Flutter的编译命令
RunCommand "${FLUTTER_ROOT}/bin/flutter" --suppress-analytics \
${verbose_flag} \
build aot \
--output-dir="${build_dir}/aot" \
--target-platform=ios \
--target="${target_path}" \
--${build_mode} \
--ios-arch="${archs}" \
${local_engine_flag} \
${track_widget_creation_flag}
生成dSYM
在Release/Profile
模式下,默认会生成符号表dSYM
,用于符号的还原。方便崩溃的时候分析问题所在。不需要打入App.framework。因此,这里会将dSYM
从App.framework中剥离。
# 生成 dSYM 文件
RunCommand xcrun dsymutil -o "${build_dir}/dSYMs.noindex/App.framework.dSYM" "${app_framework}/App"
StreamOutput " ├─Stripping debug symbols..."
# 剥离调试符号表
RunCommand xcrun strip -x -S "${derived_dir}/App.framework/App"
StreamOutput "done"
Debug
- Debug模式会包含程序的
JIT编译快照
。
RunCommand eval "$(echo "static const int Moo = 88;" | xcrun clang -x c \
${
arch_flags} \
-dynamiclib \
-Xlinker -rpath -Xlinker '@executable_path/Frameworks' \
-Xlinker -rpath -Xlinker '@loader_path/Frameworks' \
-install_name '@rpath/App.framework/App' \
-o "${derived_dir}/App.framework/App" -)"
static const int Moo = 88;
这一句我暂时也不知道什么用的,先放着,之后分析。╮(╯▽╰)╭
编译资源包
# 编译资源包,若是debug模式则会包含flutter代码的JIT编译快照,此时app.framework中不含dart代码
StreamOutput " ├─Assembling Flutter resources..."
RunCommand "${FLUTTER_ROOT}/bin/flutter" --suppress-analytics \
${verbose_flag} \
build bundle \
--target-platform=ios \
--target="${target_path}" \
--${build_mode} \
--depfile="${build_dir}/snapshot_blob.bin.d" \
--asset-dir="${derived_dir}/flutter_assets" \
${precompilation_flag} \
${local_engine_flag} \
${track_widget_creation_flag}
完整源码
BuildApp() {
# xcode工程根目录,SOURCE_ROOT这个变量来自xcode工程环境
local project_path="${SOURCE_ROOT}/.."
# FLUTTER_APPLICATION_PATH flutter工程目录,该变量来自Generated.xcconfig文件
# 若FLUTTER_APPLICATION_PATH不为空则,赋值给project_path
if [[ -n "$FLUTTER_APPLICATION_PATH" ]]; then
project_path="${FLUTTER_APPLICATION_PATH}"
fi
# flutter的程序入口文件目录
local target_path="lib/main.dart"
if [[ -n "$FLUTTER_TARGET" ]]; then
target_path="${FLUTTER_TARGET}"
fi
# Use FLUTTER_BUILD_MODE if it's set, otherwise use the Xcode build configuration name
# This means that if someone wants to use an Xcode build config other than Debug/Profile/Release,
# they _must_ set FLUTTER_BUILD_MODE so we know what type of artifact to build.
# 获取编译模式
# 根据编译模式设置相应变量
# artifact_variant是后续拷贝flutter引擎的时候使用,决定引擎的版本
# 在podhelper.rb中已经把flutter引擎集成进去了,不过依赖的是flutter工程本身编译模式引入的版本,可能不同
# 所以在这个脚本之中希望能够重新引入相应模式的engine
local build_mode="$(echo "${FLUTTER_BUILD_MODE:-${CONFIGURATION}}" | tr "[:upper:]" "[:lower:]")"
local artifact_variant="unknown".
case "$build_mode" in
release*) build_mode="release"; artifact_variant="ios-release";;
profile*) build_mode="profile