現(xiàn)狀
Flutter官方的執(zhí)行方案對(duì)Flutter工程及環(huán)境有很強(qiáng)的依賴性痪宰,非Flutter的成員在對(duì)iOS主工程進(jìn)行迭代開發(fā)時(shí)需要依賴Flutter環(huán)境料扰,團(tuán)隊(duì)合作十分不便斧吐。在通過Jenkins等打包時(shí)會(huì)有各種問題蔗蹋。
官方文章地址:Add Flutter to existing apps?
基于Flutter版本1.9.1+hotfix 4
適用于以flutter module進(jìn)行開發(fā)锨天,作為iOS一個(gè)組件來引入的情況
(可以直接跳至方案查看結(jié)果)
期望
flutter 集成進(jìn)iOS項(xiàng)目脫離Flutter環(huán)境及工程,F(xiàn)lutter工程開發(fā)與iOS原生開發(fā)互不影響缔恳。
分析
Flutter執(zhí)行build之后產(chǎn)物介紹
在Flutter的module中執(zhí)行flutter build ios --release
后宝剖,我們的工程目錄里有隱藏文件夾.ios
,我們需要的Flutter產(chǎn)物基本都在其下的Flutter文件夾中歉甚。
挨個(gè)分析一下內(nèi)部我們需要的文件万细。
-
.symlinks
我們?nèi)綆斓乃饕瑑?nèi)部每個(gè)文件夾下纸泄,都包含
ios
文件夾赖钞,這個(gè)文件夾下都是一個(gè)pod庫。 -
App.framework
Flutter項(xiàng)目的Dart代碼編譯而成的Framework聘裁。
-
engine
Flutter的引擎Framework雪营,一個(gè)pod庫。
-
FlutterPluginRegistrant
Flutter三方庫的注冊(cè)入口衡便,一個(gè)pod庫献起。
-
Generated.xcconfig
Flutter的路徑信息,配置信息等镣陕。
-
podhelper.rb
pod執(zhí)行的時(shí)候用到的腳本征唬。后續(xù)會(huì)對(duì)這個(gè)文件做具體的分析
其他的文件基本都沒什么用處可以不關(guān)注
官方方案分析
官方執(zhí)行步驟:
- 在
Podfile
中加入如下代碼:
flutter_application_path = 'path/to/my_flutter/'
load File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb')
- 對(duì)每個(gè)Xcode target添加方法
install_all_flutter_pods(flutter_application_path)
target 'MyApp' do
install_all_flutter_pods(flutter_application_path)
end
target 'MyAppTests' do
install_all_flutter_pods(flutter_application_path)
end
分析:
第一步作用是設(shè)置flutter工程路徑并添加腳本podhelper.rb。
第二步作用就是執(zhí)行podhelper.rb中的方法install_all_flutter_pods茁彭,并將flutter工程路徑變量傳入。
那么下面我們來分析一下這個(gè)podhelper.rb中到底做了什么扶歪。
分析podhelper.rb
腳本代碼如下:
# Install pods needed to embed Flutter application, Flutter engine, and plugins
# from the host application Podfile.
#
# @example
# target 'MyApp' do
# install_all_flutter_pods 'my_flutter'
# end
# @param [String] flutter_application_path Path of the root directory of the Flutter module.
# Optional, defaults to two levels up from the directory of this script.
# MyApp/my_flutter/.ios/Flutter/../..
def install_all_flutter_pods(flutter_application_path = nil)
flutter_application_path ||= File.join('..', '..')
install_flutter_engine_pod
# install_flutter_plugin_pods(flutter_application_path)
install_flutter_application_pod(flutter_application_path)
end
# Install Flutter engine pod.
#
# @example
# target 'MyApp' do
# install_flutter_engine_pod
# end
def install_flutter_engine_pod
engine_dir = File.join(__dir__, 'engine')
if !File.exist?(engine_dir)
# Copy the debug engine to have something to link against if the xcode backend script has not run yet.
# CocoaPods will not embed the framework on pod install (before any build phases can generate) if the dylib does not exist.
debug_framework_dir = File.join(flutter_root, '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
# Keep pod path relative so it can be checked into Podfile.lock.
# Process will be run from project directory.
engine_pathname = Pathname.new engine_dir
project_directory_pathname = Pathname.new Dir.pwd
relative = engine_pathname.relative_path_from project_directory_pathname
pod 'Flutter', :path => relative.to_s, :inhibit_warnings => true
end
# Install Flutter plugin pods.
#
# @example
# target 'MyApp' do
# install_flutter_plugin_pods 'my_flutter'
# end
# @param [String] flutter_application_path Path of the root directory of the Flutter module.
# Optional, defaults to two levels up from the directory of this script.
# MyApp/my_flutter/.ios/Flutter/../..
#def install_flutter_plugin_pods(flutter_application_path)
# flutter_application_path ||= File.join('..', '..')
#
# # Keep pod path relative so it can be checked into Podfile.lock.
# # Process will be run from project directory.
# current_directory_pathname = Pathname.new __dir__
# project_directory_pathname = Pathname.new Dir.pwd
# relative = current_directory_pathname.relative_path_from project_directory_pathname
# pod 'FlutterPluginRegistrant', :path => File.join(relative, 'FlutterPluginRegistrant'), :inhibit_warnings => true
#
# symlinks_dir = File.join(relative, '.symlinks')
# FileUtils.mkdir_p(symlinks_dir)
# plugin_pods = parse_KV_file(File.join(flutter_application_path, '.flutter-plugins'))
# plugin_pods.map do |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'), :inhibit_warnings => true
# end
#end
# Install Flutter application pod.
#
# @example
# target 'MyApp' do
# install_flutter_application_pod '../flutter_settings_repository'
# end
# @param [String] flutter_application_path Path of the root directory of the Flutter module.
# Optional, defaults to two levels up from the directory of this script.
# MyApp/my_flutter/.ios/Flutter/../..
def install_flutter_application_pod(flutter_application_path)
app_framework_dir = File.join(__dir__, 'App.framework')
app_framework_dylib = File.join(app_framework_dir, 'App')
if !File.exist?(app_framework_dylib)
# Fake an App.framework to have something to link against if the xcode backend script has not run yet.
# CocoaPods will not embed the framework on pod install (before any build phases can run) if the dylib does not exist.
# Create a dummy dylib.
FileUtils.mkdir_p(app_framework_dir)
`echo "static const int Moo = 88;" | xcrun clang -x c -dynamiclib -o "#{app_framework_dylib}" -`
end
# Keep pod and script phase paths relative so they can be checked into source control.
# Process will be run from project directory.
current_directory_pathname = Pathname.new __dir__
project_directory_pathname = Pathname.new Dir.pwd
relative = current_directory_pathname.relative_path_from project_directory_pathname
pod 'flutter_module', :path => relative.to_s, :inhibit_warnings => true
# flutter_export_environment_path = File.join('${SRCROOT}', relative, 'flutter_export_environment.sh');
# script_phase :name => 'Run Flutter Build Script',
# :script => "set -e\nset -u\nsource \"#{flutter_export_environment_path}\"\n../xcode_backend.sh build",
## :script => "set -e\nset -u\nsource \"#{flutter_export_environment_path}\"\n\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/xcode_backend.sh build",
#
# :input_files => [
# File.join('${SRCROOT}', flutter_application_path, '.metadata'),
# File.join('${SRCROOT}', relative, 'App.framework', 'App'),
# File.join('${SRCROOT}', relative, 'engine', 'Flutter.framework', 'Flutter'),
# flutter_export_environment_path
# ],
# :execution_position => :before_compile
end
#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
#def flutter_root
# generated_xcode_build_settings = parse_KV_file(File.join(__dir__, 'Generated.xcconfig'))
# if generated_xcode_build_settings.empty?
# puts "Generated.xcconfig must exist. Make sure `flutter pub get` is executed in the Flutter module."
# exit
# end
# generated_xcode_build_settings.map { |p|
# if p[:name] == 'FLUTTER_ROOT'
# return p[:path]
# end
# }
#end
解釋一下理肺,這個(gè)腳本未注釋的部分做了兩件事:
- 引入Flutter引擎
install_flutter_engine_pod
- 引入Flutter工程編譯后的Framework摄闸,
install_flutter_application_pod
如果是執(zhí)行
flutter build ios --debug
會(huì)把這個(gè)腳本里的已注釋代碼部分取消注釋。原因是release模式會(huì)把第三方庫的東西都統(tǒng)一編譯到App.framework內(nèi)妹萨,debug不會(huì)年枕,所以在debug時(shí)會(huì)把第三方庫分別引入。
方案
綜上乎完,我們的方案步驟如下:
在flutter_module工程里執(zhí)行
flutter build ios --release
方法熏兄,在工程目錄里找到.ios
文件夾復(fù)制出來Flutter文件夾下的engine目錄,App.framework及flutter module的podspec文件树姨。
將app.framework及podspec放入一個(gè)文件夾摩桶,engine放入另一個(gè)文件夾。
-
podfile里面添加
pod 'Flutter', :path => 'path to engine' pod 'flutter module 的name', :path => 'path to app.framework'
執(zhí)行pod install就好了
此外帽揪,也可以把這兩個(gè)pod庫放入git倉庫等進(jìn)行管理硝清。
參考文章:
Flutter遠(yuǎn)程依賴簡(jiǎn)單實(shí)踐
Flutter iOS 混合工程自動(dòng)化
歡迎大家拍磚