Flutter以framework集成入iOS項(xiàng)目方案

現(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文件夾中歉甚。

image-20191031164025401.png

挨個(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í)行步驟:
  1. Podfile中加入如下代碼:
flutter_application_path = 'path/to/my_flutter/'
load File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb')
  1. 對(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ì)把第三方庫分別引入。

方案

綜上乎完,我們的方案步驟如下:

  1. 在flutter_module工程里執(zhí)行flutter build ios --release方法熏兄,在工程目錄里找到.ios文件夾

  2. 復(fù)制出來Flutter文件夾下的engine目錄,App.framework及flutter module的podspec文件树姨。

  3. 將app.framework及podspec放入一個(gè)文件夾摩桶,engine放入另一個(gè)文件夾。

  4. 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)化

歡迎大家拍磚

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市转晰,隨后出現(xiàn)的幾起案子芦拿,更是在濱河造成了極大的恐慌,老刑警劉巖查邢,帶你破解...
    沈念sama閱讀 211,265評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蔗崎,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡扰藕,警方通過查閱死者的電腦和手機(jī)缓苛,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,078評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來实胸,“玉大人他嫡,你說我怎么就攤上這事÷辏” “怎么了钢属?”我有些...
    開封第一講書人閱讀 156,852評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)门躯。 經(jīng)常有香客問我淆党,道長(zhǎng),這世上最難降的妖魔是什么讶凉? 我笑而不...
    開封第一講書人閱讀 56,408評(píng)論 1 283
  • 正文 為了忘掉前任染乌,我火速辦了婚禮,結(jié)果婚禮上懂讯,老公的妹妹穿的比我還像新娘荷憋。我一直安慰自己,他們只是感情好褐望,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,445評(píng)論 5 384
  • 文/花漫 我一把揭開白布勒庄。 她就那樣靜靜地躺著串前,像睡著了一般。 火紅的嫁衣襯著肌膚如雪实蔽。 梳的紋絲不亂的頭發(fā)上荡碾,一...
    開封第一講書人閱讀 49,772評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音局装,去河邊找鬼坛吁。 笑死,一個(gè)胖子當(dāng)著我的面吹牛铐尚,可吹牛的內(nèi)容都是我干的拨脉。 我是一名探鬼主播,決...
    沈念sama閱讀 38,921評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼塑径,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼女坑!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起统舀,我...
    開封第一講書人閱讀 37,688評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤匆骗,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后誉简,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體碉就,經(jīng)...
    沈念sama閱讀 44,130評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,467評(píng)論 2 325
  • 正文 我和宋清朗相戀三年闷串,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了瓮钥。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,617評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡烹吵,死狀恐怖碉熄,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情肋拔,我是刑警寧澤锈津,帶...
    沈念sama閱讀 34,276評(píng)論 4 329
  • 正文 年R本政府宣布,位于F島的核電站凉蜂,受9級(jí)特大地震影響琼梆,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜窿吩,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,882評(píng)論 3 312
  • 文/蒙蒙 一茎杂、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧纫雁,春花似錦煌往、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,740評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽悼粮。三九已至,卻和暖如春曾棕,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背菜循。 一陣腳步聲響...
    開封第一講書人閱讀 31,967評(píng)論 1 265
  • 我被黑心中介騙來泰國打工翘地, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人癌幕。 一個(gè)月前我還...
    沈念sama閱讀 46,315評(píng)論 2 360
  • 正文 我出身青樓衙耕,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國和親勺远。 傳聞我的和親對(duì)象是個(gè)殘疾皇子橙喘,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,486評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容