Flutter提供的混編方案直接依賴於Flutter工程和Flutter環境,非Flutte團隊成員沒法脫離Flutter環境進行開發,團隊合做成本加劇。ios
Flutter默認的混編方式:不光依賴於flutter工程中的flutter產物,還依賴於flutter SDK中的xcode_backend.sh腳本。咱們但願可以作到當項目混編的時候,沒有開發flutter的團隊成員可以徹底脫離flutter,不須要flutter項目代碼和安裝flutter環境;而寫flutter的團隊成員可以按照原有的混編方式以方便開發和調試。git
帶着這個目標,咱們來一步一步分析混編過程。github
看圖,看圖,這個是Flutter編譯生成的Runner工做空間。iOS依賴的Flutter產物都在這個Flutter文件夾中。 依次來介紹一下這些傢伙:shell
.symlinks
Flutter的三方包package,是各個文件夾的索引,指向了本地的pub緩存區的包。每個包裏面都包含一個iOS的本地pod倉庫,在包的iOS
文件夾中。於是Flutter包的依賴方式直接pod導入便可。數組
App.framework
由Flutter項目的Dart代碼編譯而成,僅僅是framework。集成的時候能夠本身作成本地pod庫也能夠直接拷貝進app包,而後簽名。xcode
AppFrameworkInfi.plist
Flutter的一些可有可無的配置信息,忽略緩存
engine
Flutter渲染引擎,也是一個本地pod倉庫ruby
flutter_assets
Flutter的資源文件,圖片等,集成時拷貝進app包便可bash
FlutterPluginRegistrant
Fluttter三方包的註冊代碼,有引入三方包時,須要引入這個,也是一個本地pod倉庫微信
Generated.xcconfig
Flutter相關的一些路徑信息,配置信息等。整個文件會被引入到iOS工程的各個*.xcconfig
配置文件中。這些配置信息,在xcode runscript中引入的flutter編譯嵌入腳本xcode_backend.sh
中會使用到。固然你也能夠修改腳本,去除對這個文件的依賴。
podhelper.rb
ruby腳本,包含了一個 cocoapod
鉤子,在pod的安裝過程當中引入flutter的全部本地庫依賴,並在每一個*.xcconfig
配置文件中寫進 『導入Generated.xcconfig
』的代碼,如#include '.../Generated.xcconfig')
;
本質上,理清依賴的前提是 閱讀腳本,提早貼出來是爲了分析腳本的時候可以更好地理解過程。
默認的混編方案流程是
#Flutter工程路徑
flutter_application_path = 'flutter_project_dir'
#讀取 podhelper.rb 的Ruby代碼在當前目錄執行
eval(File.read(File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb')), binding)
複製代碼
"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh" build
"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh" embed
複製代碼
而後pod install
便可。
一切的祕㊙️就在這兩個腳本中。
這個Ruby腳本只有七十多行,鑑於不是每一個人都熟悉Ruby腳本,我詳細註釋了一下:
# 解析文件內容爲字典數組
# 文件內容格式爲 A=B換行C=D 的類型
# 如 A=B
# C=D
# 解析爲:
# {"A"="B","C"="D"}
def parse_KV_file(file, separator='=')
file_abs_path = File.expand_path(file)
if !File.exists? file_abs_path
return [];
end
pods_array = []
skip_line_start_symbols = ["#", "/"]
File.foreach(file_abs_path) { |line|
next if skip_line_start_symbols.any? { |symbol| line =~ /^\s*#{symbol}/ }
plugin = line.split(pattern=separator)
if plugin.length == 2
podname = plugin[0].strip()
path = plugin[1].strip()
podpath = File.expand_path("#{path}", file_abs_path)
pods_array.push({:name => podname, :path => podpath});
else
puts "Invalid plugin specification: #{line}"
end
}
return pods_array
end
# 這是個函數,功能是從flutter工程生成的iOS依賴目錄中的Generated.xcconfig文件解析
# FLUTTER_ROOT目錄,也就是你安裝的flutter SDKf根目錄
def flutter_root(f)
generated_xcode_build_settings = parse_KV_file(File.join(f, File.join('.ios', 'Flutter', 'Generated.xcconfig')))
if generated_xcode_build_settings.empty?
puts "Generated.xcconfig must exist. Make sure `flutter packages get` is executed in ${f}."
exit
end
generated_xcode_build_settings.map { |p|
if p[:name] == 'FLUTTER_ROOT'
return p[:path]
end
}
end
# 代碼入口在這裏
# flutter工程目錄,若是沒有值,則取向上退兩級的目錄(也就是Flutter生成整個iOS項目的狀況)
flutter_application_path ||= File.join(__dir__, '..', '..')
# Flutter生成的framework目錄,引擎庫,編譯完成的代碼庫等幾乎全部iOS項目的依賴都放在這裏
framework_dir = File.join(flutter_application_path, '.ios', 'Flutter')
# flutter引擎目錄
engine_dir = File.join(framework_dir, 'engine')
# 若是引擎目錄不存在就去 flutter SDK目錄中拷貝一份,引擎是一個本地pod庫
# File.join,功能是拼接文件目錄
if !File.exist?(engine_dir)
# 這個是debug版本的flutter引擎目錄,release的最後一級爲「ios-release」,profile版本爲ios-profile
debug_framework_dir = File.join(flutter_root(flutter_application_path), 'bin', 'cache', 'artifacts', 'engine', 'ios')
FileUtils.mkdir_p(engine_dir)
FileUtils.cp_r(File.join(debug_framework_dir, 'Flutter.framework'), engine_dir)
FileUtils.cp(File.join(debug_framework_dir, 'Flutter.podspec'), engine_dir)
end
# 這個應該每一個人都很熟悉
#加載flutter引擎pod庫
pod 'Flutter', :path => engine_dir
#加載flutter三方庫的註冊代碼庫
pod 'FlutterPluginRegistrant', :path => File.join(framework_dir, 'FlutterPluginRegistrant')
#flutter三方庫的快捷方式文件夾,最終索引到pub緩存中的各個庫的目錄
symlinks_dir = File.join(framework_dir, '.symlinks')
FileUtils.mkdir_p(symlinks_dir)
#解析.flutter-plugins文件,獲取當前flutter工程用到的三方庫
plugin_pods = parse_KV_file(File.join(flutter_application_path, '.flutter-plugins'))
#加載當前工程用到的每個pod庫
plugin_pods.map { |r|
symlink = File.join(symlinks_dir, r[:name])
FileUtils.rm_f(symlink)
File.symlink(r[:path], symlink)
pod r[:name], :path => File.join(symlink, 'ios')
}
# 修改全部pod庫的 ENABLE_BITCODE 爲 NO,含原生代碼引用的pod庫
# 並在每個pod庫的.xcconfig文件中引入Generated.xcconfig文件
# 該文件中包含一系列flutter須要用到的變量,具體在xcode_backend.sh腳本中會使用到
post_install do |installer|
installer.pods_project.targets.each do |target|
target.build_configurations.each do |config|
config.build_settings['ENABLE_BITCODE'] = 'NO'
xcconfig_path = config.base_configuration_reference.real_path
File.open(xcconfig_path, 'a+') do |file|
file.puts "#include \"#{File.realpath(File.join(framework_dir, 'Generated.xcconfig'))}\""
end
end
end
end
複製代碼
總結一下,這個Ruby腳本舊作了如下這幾件事情
至此,還缺乏Dart代碼庫以及flutter引入的資源,這個在xcode_backend.sh
腳本實現了。這個腳本在flutter SDK的packages/flutter_tools/bin
中
一樣看一下全部代碼,以及詳細註釋:
#!/bin/bash
# Copyright 2016 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
RunCommand() {
if [[ -n "$VERBOSE_SCRIPT_LOGGING" ]]; then
echo "♦ $*"
fi
"$@"
return $?
}
# When provided with a pipe by the host Flutter build process, output to the
# pipe goes to stdout of the Flutter build process directly.
StreamOutput() {
if [[ -n "$SCRIPT_OUTPUT_STREAM_FILE" ]]; then
echo "$1" > $SCRIPT_OUTPUT_STREAM_FILE
fi
}
EchoError() {
echo "$@" 1>&2
}
# 驗證路徑中的資源是否存在
AssertExists() {
if [[ ! -e "$1" ]]; then
if [[ -h "$1" ]]; then
EchoError "The path $1 is a symlink to a path that does not exist"
else
EchoError "The path $1 does not exist"
fi
exit -1
fi
return 0
}
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"; artifact_variant="ios-profile";;
debug*) build_mode="debug"; artifact_variant="ios";;
*)
EchoError "========================================================================"
EchoError "ERROR: Unknown FLUTTER_BUILD_MODE: ${build_mode}."
EchoError "Valid values are 'Debug', 'Profile', or 'Release' (case insensitive)."
EchoError "This is controlled by the FLUTTER_BUILD_MODE environment varaible."
EchoError "If that is not set, the CONFIGURATION environment variable is used."
EchoError ""
EchoError "You can fix this by either adding an appropriately named build"
EchoError "configuration, or adding an appriate value for FLUTTER_BUILD_MODE to the"
EchoError ".xcconfig file for the current build configuration (${CONFIGURATION})."
EchoError "========================================================================"
exit -1;;
esac
# Archive builds (ACTION=install) should always run in release mode.
if [[ "$ACTION" == "install" && "$build_mode" != "release" ]]; then
EchoError "========================================================================"
EchoError "ERROR: Flutter archive builds must be run in Release mode."
EchoError ""
EchoError "To correct, ensure FLUTTER_BUILD_MODE is set to release or run:"
EchoError "flutter build ios --release"
EchoError ""
EchoError "then re-run Archive from Xcode."
EchoError "========================================================================"
exit -1
fi
#flutter引擎的詳細地址
local framework_path="${FLUTTER_ROOT}/bin/cache/artifacts/engine/${artifact_variant}"
AssertExists "${framework_path}"
AssertExists "${project_path}"
#flutter的目標存放目錄
local derived_dir="${SOURCE_ROOT}/Flutter"
if [[ -e "${project_path}/.ios" ]]; then
derived_dir="${project_path}/.ios/Flutter"
fi
RunCommand mkdir -p -- "$derived_dir"
AssertExists "$derived_dir"
RunCommand rm -rf -- "${derived_dir}/App.framework"
local local_engine_flag=""
local flutter_framework="${framework_path}/Flutter.framework"
local flutter_podspec="${framework_path}/Flutter.podspec"
# 若是本地的引擎存在,則引擎使用此路徑,後續拷貝引擎從這個目錄拷貝
if [[ -n "$LOCAL_ENGINE" ]]; then
if [[ $(echo "$LOCAL_ENGINE" | tr "[:upper:]" "[:lower:]") != *"$build_mode"* ]]; then
EchoError "========================================================================"
EchoError "ERROR: Requested build with Flutter local engine at '${LOCAL_ENGINE}'"
EchoError "This engine is not compatible with FLUTTER_BUILD_MODE: '${build_mode}'."
EchoError "You can fix this by updating the LOCAL_ENGINE environment variable, or"
EchoError "by running:"
EchoError " flutter build ios --local-engine=ios_${build_mode}"
EchoError "or"
EchoError " flutter build ios --local-engine=ios_${build_mode}_unopt"
EchoError "========================================================================"
exit -1
fi
local_engine_flag="--local-engine=${LOCAL_ENGINE}"
flutter_framework="${LOCAL_ENGINE}/Flutter.framework"
flutter_podspec="${LOCAL_ENGINE}/Flutter.podspec"
fi
#複製Flutter engine 到依賴目錄
if [[ -e "${project_path}/.ios" ]]; then
RunCommand rm -rf -- "${derived_dir}/engine"
mkdir "${derived_dir}/engine"
RunCommand cp -r -- "${flutter_podspec}" "${derived_dir}/engine"
RunCommand cp -r -- "${flutter_framework}" "${derived_dir}/engine"
RunCommand find "${derived_dir}/engine/Flutter.framework" -type f -exec chmod a-w "{}" \;
else
RunCommand rm -rf -- "${derived_dir}/Flutter.framework"
RunCommand cp -r -- "${flutter_framework}" "${derived_dir}"
RunCommand find "${derived_dir}/Flutter.framework" -type f -exec chmod a-w "{}" \;
fi
# 切換腳本執行目錄到flutter工程,以便執行flutter命令
RunCommand pushd "${project_path}" > /dev/null
AssertExists "${target_path}"
# 是否須要詳細日誌的輸出標記
local verbose_flag=""
if [[ -n "$VERBOSE_SCRIPT_LOGGING" ]]; then
verbose_flag="--verbose"
fi
#flutter build 目錄
local build_dir="${FLUTTER_BUILD_DIR:-build}"
#是否檢測weidget的建立,release模式不支持此參數
local track_widget_creation_flag=""
if [[ -n "$TRACK_WIDGET_CREATION" ]]; then
track_widget_creation_flag="--track-widget-creation"
fi
# 非debug模式:執行flutter build aot ios ……編譯dart代碼成app.framework
# 生成 dSYM 文件
# 剝離調試符號表
# debug模式:把『static const int Moo = 88;』這句代碼打成app.framework,
# 直接使用JIT模式的快照
if [[ "${build_mode}" != "debug" ]]; then
StreamOutput " ├─Building Dart code..."
# Transform ARCHS to comma-separated list of target architectures.
local archs="${ARCHS// /,}"
if [[ $archs =~ .*i386.* || $archs =~ .*x86_64.* ]]; then
EchoError "========================================================================"
EchoError "ERROR: Flutter does not support running in profile or release mode on"
EchoError "the Simulator (this build was: '$build_mode')."
EchoError "You can ensure Flutter runs in Debug mode with your host app in release"
EchoError "mode by setting FLUTTER_BUILD_MODE=debug in the .xcconfig associated"
EchoError "with the ${CONFIGURATION} build configuration."
EchoError "========================================================================"
exit -1
fi
#執行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}
if [[ $? -ne 0 ]]; then
EchoError "Failed to build ${project_path}."
exit -1
fi
StreamOutput "done"
local app_framework="${build_dir}/aot/App.framework"
RunCommand cp -r -- "${app_framework}" "${derived_dir}"
StreamOutput " ├─Generating dSYM file..."
# Xcode calls `symbols` during app store upload, which uses Spotlight to
# find dSYM files for embedded frameworks. When it finds the dSYM file for
# `App.framework` it throws an error, which aborts the app store upload.
# To avoid this, we place the dSYM files in a folder ending with ".noindex",
# which hides it from Spotlight, https://github.com/flutter/flutter/issues/22560.
RunCommand mkdir -p -- "${build_dir}/dSYMs.noindex"
# 生成 dSYM 文件
RunCommand xcrun dsymutil -o "${build_dir}/dSYMs.noindex/App.framework.dSYM" "${app_framework}/App"
if [[ $? -ne 0 ]]; then
EchoError "Failed to generate debug symbols (dSYM) file for ${app_framework}/App."
exit -1
fi
StreamOutput "done"
StreamOutput " ├─Stripping debug symbols..."
# 剝離調試符號表
RunCommand xcrun strip -x -S "${derived_dir}/App.framework/App"
if [[ $? -ne 0 ]]; then
EchoError "Failed to strip ${derived_dir}/App.framework/App."
exit -1
fi
StreamOutput "done"
else
RunCommand mkdir -p -- "${derived_dir}/App.framework"
# Build stub for all requested architectures.
local arch_flags=""
# 獲取當前調試模式的架構參數
#模擬器是x86_64
#真機則根據實際的架構armv7或arm64
read -r -a archs <<< "$ARCHS"
for arch in "${archs[@]}"; do
arch_flags="${arch_flags}-arch $arch "
done
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" -)"
fi
#嵌入Info.plist
local plistPath="${project_path}/ios/Flutter/AppFrameworkInfo.plist"
if [[ -e "${project_path}/.ios" ]]; then
plistPath="${project_path}/.ios/Flutter/AppFrameworkInfo.plist"
fi
RunCommand cp -- "$plistPath" "${derived_dir}/App.framework/Info.plist"
local precompilation_flag=""
if [[ "$CURRENT_ARCH" != "x86_64" ]] && [[ "$build_mode" != "debug" ]]; then
precompilation_flag="--precompiled"
fi
# 編譯 資源包,如果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}
if [[ $? -ne 0 ]]; then
EchoError "Failed to package ${project_path}."
exit -1
fi
StreamOutput "done"
StreamOutput " └─Compiling, linking and signing..."
RunCommand popd > /dev/null
echo "Project ${project_path} built and packaged successfully."
return 0
}
# Returns the CFBundleExecutable for the specified framework directory.
GetFrameworkExecutablePath() {
local framework_dir="$1"
local plist_path="${framework_dir}/Info.plist"
local executable="$(defaults read "${plist_path}" CFBundleExecutable)"
echo "${framework_dir}/${executable}"
}
# Destructively thins the specified executable file to include only the
# specified architectures.
LipoExecutable() {
local executable="$1"
shift
# Split $@ into an array.
read -r -a archs <<< "$@"
# Extract architecture-specific framework executables.
local all_executables=()
for arch in "${archs[@]}"; do
local output="${executable}_${arch}"
local lipo_info="$(lipo -info "${executable}")"
if [[ "${lipo_info}" == "Non-fat file:"* ]]; then
if [[ "${lipo_info}" != *"${arch}" ]]; then
echo "Non-fat binary ${executable} is not ${arch}. Running lipo -info:"
echo "${lipo_info}"
exit 1
fi
else
lipo -output "${output}" -extract "${arch}" "${executable}"
if [[ $? == 0 ]]; then
all_executables+=("${output}")
else
echo "Failed to extract ${arch} for ${executable}. Running lipo -info:"
lipo -info "${executable}"
exit 1
fi
fi
done
# Generate a merged binary from the architecture-specific executables.
# Skip this step for non-fat executables.
if [[ ${#all_executables[@]} > 0 ]]; then
local merged="${executable}_merged"
lipo -output "${merged}" -create "${all_executables[@]}"
cp -f -- "${merged}" "${executable}" > /dev/null
rm -f -- "${merged}" "${all_executables[@]}"
fi
}
# Destructively thins the specified framework to include only the specified
# architectures.
ThinFramework() {
local framework_dir="$1"
shift
local plist_path="${framework_dir}/Info.plist"
local executable="$(GetFrameworkExecutablePath "${framework_dir}")"
LipoExecutable "${executable}" "$@"
}
ThinAppFrameworks() {
local app_path="${TARGET_BUILD_DIR}/${WRAPPER_NAME}"
local frameworks_dir="${app_path}/Frameworks"
[[ -d "$frameworks_dir" ]] || return 0
find "${app_path}" -type d -name "*.framework" | while read framework_dir; do
ThinFramework "$framework_dir" "$ARCHS"
done
}
# Adds the App.framework as an embedded binary and the flutter_assets as
# resources.
# 主要作了這幾件事:
# 複製flutter_asserts到app包
# 複製Flutter引擎到app包
# 複製dart代碼編譯產物app.framework到app包
# 簽名兩個framework
EmbedFlutterFrameworks() {
AssertExists "${FLUTTER_APPLICATION_PATH}"
# Prefer the hidden .ios folder, but fallback to a visible ios folder if .ios
# doesn't exist.
local flutter_ios_out_folder="${FLUTTER_APPLICATION_PATH}/.ios/Flutter"
local flutter_ios_engine_folder="${FLUTTER_APPLICATION_PATH}/.ios/Flutter/engine"
if [[ ! -d ${flutter_ios_out_folder} ]]; then
flutter_ios_out_folder="${FLUTTER_APPLICATION_PATH}/ios/Flutter"
flutter_ios_engine_folder="${FLUTTER_APPLICATION_PATH}/ios/Flutter"
fi
AssertExists "${flutter_ios_out_folder}"
# Copy the flutter_assets to the Application's resources.
AssertExists "${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/"
RunCommand cp -r -- "${flutter_ios_out_folder}/flutter_assets" "${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/"
# Embed App.framework from Flutter into the app (after creating the Frameworks directory
# if it doesn't already exist).
local xcode_frameworks_dir=${BUILT_PRODUCTS_DIR}"/"${PRODUCT_NAME}".app/Frameworks"
RunCommand mkdir -p -- "${xcode_frameworks_dir}"
RunCommand cp -Rv -- "${flutter_ios_out_folder}/App.framework" "${xcode_frameworks_dir}"
# Embed the actual Flutter.framework that the Flutter app expects to run against,
# which could be a local build or an arch/type specific build.
# Remove it first since Xcode might be trying to hold some of these files - this way we're
# sure to get a clean copy.
RunCommand rm -rf -- "${xcode_frameworks_dir}/Flutter.framework"
RunCommand cp -Rv -- "${flutter_ios_engine_folder}/Flutter.framework" "${xcode_frameworks_dir}/"
# Sign the binaries we moved.
local identity="${EXPANDED_CODE_SIGN_IDENTITY_NAME:-$CODE_SIGN_IDENTITY}"
if [[ -n "$identity" && "$identity" != "\"\"" ]]; then
RunCommand codesign --force --verbose --sign "${identity}" -- "${xcode_frameworks_dir}/App.framework/App"
RunCommand codesign --force --verbose --sign "${identity}" -- "${xcode_frameworks_dir}/Flutter.framework/Flutter"
fi
}
# 主函數入口
# 如下結合xcode run srcript中的腳本就很好理解
# 編譯、嵌入
#"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh" build
#"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh" embed
if [[ $# == 0 ]]; then # 若是不帶參數則直接執行BuildApp函數
# Backwards-compatibility: if no args are provided, build.
BuildApp
else # 不然執行case語句
case $1 in
"build")
BuildApp ;;
"thin")
ThinAppFrameworks ;;
"embed")
EmbedFlutterFrameworks ;;
esac
fi
複製代碼
一樣,總結一下,這個shell腳本作了如下事情
static const int Moo = 88;
爲App.framework(猜想此行代碼爲JIT/AOT模式切換標記)這一節大部分都貼代碼了,若是是簡單講過程可能不是很好理解,詳細的你們仍是直接讀腳本吧。若是看不懂腳本,看註釋也是可以瞭解個大概。
依賴以及過程都理清楚了,最後是時候說方案了。 回頭看一塊兒指望
到了這裏,咱們仍是但願可以作的更好一點,就是可以實現兩種模式的切換。大概畫了一個圖,你們將就看一下。
方案大概的解決方法就是:
徹底脫離Flutter環境:(圖中實線流程部分) 利用腳本將全部的依賴編譯結果從Flutter工程中剝離出來,放到iOS工程目錄下。iOS native直接依賴此目錄,再也不編譯,便可以脫離Flutter環境了。(環境能夠直接是release,由於脫離Flutter的環境不會去調試Flutter代碼的。)
直接依賴Flutter工程:(圖中虛線流程部分) 直接依賴時,pod對Flutter的依賴都直接指向了Flutter工程;另外就是xcode_backend.sh
會去從新編譯Flutter代碼,Flutter資源並嵌入app;Flutter引擎也會從新嵌入相應模式的版本。
直接依賴Flutter工程的方式,這個大同小異,都是直接或間接指向Flutter工程。這裏重點討論徹底脫離Flutter環境的方案。
鹹魚團隊本身也提到存在如下問題
這個方案相比鹹魚的方案解決了原生依賴Flutter庫版本號的問題。放在原生之中的Flutter依賴直接歸爲原生管理,不須要獨立的版本。這個依賴拿到的是Flutter開發成員發佈的代碼,通常狀況下都是對應分支的最新flutter代碼編譯產物。 如iOS的dev對應Flutter的dev,齊頭並進,版本管理上就會簡單的多。
可是一樣會有Flutter依賴更新不及時等這些其餘問題,有待進一步調研和實踐。
歡迎大佬指點,若有疑問,能夠在評論中提出來你們一塊兒討論。
延伸閱讀:
[Flutter試用報告](www.jianshu.com/p/b32538e68…
參考文章: 閒魚Fultter混合工程持續集成的最佳實踐
筆者和朋友作了淘寶優惠券公衆號,購物領券省錢,幫忙關注一下。