iOS 原生項(xiàng)目集成Flutter 之 編譯產(chǎn)物收集

前言

在之前的開發(fā)過程中,我們一直采用的是Flutter官方推薦的方式集成到iOS原生項(xiàng)目中。這種方式的弊端很明顯:對原生工程的侵入太大,并且每次在編譯過程中都會重新生成Flutter的編譯文件,在多人開發(fā)的項(xiàng)目中非常影響開發(fā)效率。所以現(xiàn)改為另一種方式集成:以編譯產(chǎn)物的方式集成到原生項(xiàng)目。

目錄

一、Flutter的module集成方式

  • 創(chuàng)建一個(gè)module工程
  • Flutter通過cocoapods集成到項(xiàng)目中
  • Xcode中添加Flutter Script腳本
  • 關(guān)閉Xcode中的bitcode選項(xiàng)

二、了解Flutter編譯過程

  • 了解podhelper.rbruby文件
  • 了解xcode_backend.shshell文件
  • 總結(jié)Flutter集成過程

三、使用sh腳本收集Flutter編譯產(chǎn)物

  • 找到對應(yīng)文件位置
  • 編寫sh腳本文件
  • 為編譯產(chǎn)物配置podspec文件
  • iOS原生項(xiàng)目集成Flutter產(chǎn)物
  • 注意事項(xiàng)

四、參考文章


一、Flutter的module集成方式

前面已經(jīng)提到我們之前采用的是Flutter官方推薦的方式集成到iOS原生項(xiàng)目中,所以Flutter模塊是一個(gè)module工程。即:

1、創(chuàng)建一個(gè)module工程
$ cd some/path/
$ flutter create -t module my_flutter
2、將Flutter通過cocoapods集成到項(xiàng)目中
//將以下代碼添加到Podfle文件中,并執(zhí)行pod install
flutter_application_path = 'path/to/my_flutter/'
load File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb')
3、在Xcode中添加Flutter Script腳本
"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh" build
"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh" embed
Flutter Script
4、關(guān)閉Xcode中的bitcode選項(xiàng)
bitcode

至此,F(xiàn)lutter已集成完成,此處不涉及代碼。

二、了解Flutter編譯過程

在進(jìn)行Flutter編譯產(chǎn)物收集之前,我們需要了解Flutter在編譯過程中進(jìn)行了哪些步驟。

1、了解podhelper.rbruby文件

首先Flutter在Podfile中添加了一個(gè)podhelper.rb的ruby文件。

podhelper.rb

podhelper.rb文件與Podfile文件都為ruby語法。該文件主要是通過Flutter.podspec文件將Flutter.framework庫以及FlutterPluginRegistrant插件注冊類文件導(dǎo)入進(jìn)來,并且獲取每個(gè)Plugin模塊中的xx.podspec文件將插件中的文件導(dǎo)入。各個(gè)插件模塊以及路徑均保存在module工程中的.flutter-plugins文件中。

執(zhí)行完pod install之后,文件目錄如下

pod文件目錄

2、了解xcode_backend.shshell文件

xcode_backend文件中包含三個(gè)主要的方法:BuildAppThinAppFrameworksEmbedFlutterFrameworks

"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh" build
"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh" embed

Xcode中,首先傳入了build參數(shù)

xcode_backend.sh

xcode_backend腳本先執(zhí)行BuildApp方法,下面了解下BuildApp主要進(jìn)行了哪些操作:

Flutter編譯

方法開始進(jìn)行了文件以及路徑檢測,確認(rèn)完成后開始進(jìn)行Flutter aot(Ahead Of Time)模式編譯。并將編譯后的module工程/build/aot文件夾下的App.framework拷貝到module工程/.ios/Flutter目錄下。

打包資源文件

接著build bundle進(jìn)行資源文件打包,存放路徑在App.framework/flutter_assets中。
BuildApp方法執(zhí)行完成后,開始執(zhí)行

"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh" embed

傳入embed參數(shù), 執(zhí)行EmbedFlutterFrameworks方法。

EmbedFlutterFrameworks方法

該方法的主要目的是將App.frameworkFlutter.framework拷貝到項(xiàng)目的xx.app包中的Frameworks目錄下。

ThinAppFrameworks 方式是移除不需要的framework架構(gòu)。

3、總結(jié)Flutter集成過程

經(jīng)過上面幾個(gè)步驟,我們可以總結(jié)出Flutter集成的幾個(gè)主要依賴關(guān)系:
1、Flutter框架層主要依賴于Flutter.frameworkApp.framework兩個(gè)庫,Flutter.frameworkFlutter框架的核心,App.framework則包含的是我們的dart源碼二進(jìn)制文件以及bundle資源文件。
2、Flutter的插件注冊類FlutterPluginRegistrant以及Plugin模塊的iOS原生插件代碼。

三、使用sh腳本收集Flutter編譯產(chǎn)物

1、找到對應(yīng)文件位置

在對編譯產(chǎn)物進(jìn)行收集之前,我們需要了解Flutter編譯產(chǎn)物所在的目標(biāo)位置。

module工程目錄

在對Flutter進(jìn)行了build操作之后,module工程/.ios/Flutter目錄下會生成的文件如上圖。我們可以找到Flutter的兩個(gè)依賴庫Flutter.frameworkApp.framework

某個(gè)plugin目錄

上圖是其中一個(gè)Plugin倉庫的文件目錄結(jié)構(gòu),我們主要收集的內(nèi)容即為Classes目錄及以下的全部文件。

2、編寫sh腳本文件

需要修改腳本文件中的framework_dir文件位置

#! /bin/bash

echo "===  正在進(jìn)行編譯  ==="
flutter build ios --release

framework_dir="保存編譯產(chǎn)物的位置/build_framework"
 echo "===清理文件==="
rm -rf "$framework_dir/App.framework"
rm -rf "$framework_dir/Flutter.framework"
echo "===正在拷貝 App.framework和Flutter.framework==="
cp -r "./.ios/Flutter/App.framework" $framework_dir
cp -r "./.ios/Flutter/engine/Flutter.framework" $framework_dir

echo "===拷貝資源文件==="
resourcesDestPath=${framework_dir}/resources
resourcesOriginPath=${framework_dir}/App.framework/flutter_assets
rm -rf $resourcesDestPath
mkdir -p $resourcesDestPath
cp -af $resourcesOriginPath $resourcesDestPath

echo "===拷貝注冊器類:FlutterPluginRegistrant==="
regist_dir="./.ios/Flutter/FlutterPluginRegistrant/Classes/"
model_class=${framework_dir}/flutter/FlutterPluginRegistrant
mkdir -p $model_class
cp -r $regist_dir $model_class

temp_dir="${framework_dir}/temp"
mkdir -p ${temp_dir}

current_path="$PWD"

# 遍歷.flutter-plugins文件
cd /Users/apple/Desktop/HealthBuddy/hb_flutter_module
cat .flutter-plugins | while read line
    do
    array=(${line//=/ })
        plugin_name=${array[0]}
        echo "===修改注冊器(修正引用)==="
        perl -pi -e "s|\<${plugin_name}\/|\"|g" ${model_class}/GeneratedPluginRegistrant.m
        perl -pi -e "s|.h\>|.h\"|g" ${model_class}/GeneratedPluginRegistrant.m
        temp_library=${temp_dir}/lib${plugin_name}.a

        cd $current_path
        echo "===拷貝 ${plugin_name} 文件==="
        plugin=${framework_dir}/${plugin_name}
        rm -rf $plugin
        classes=${array[1]}ios/Classes
        class=$framework_dir/Classes/flutter/${plugin_name}
        rm -rf $class
        mkdir -p $class
        cp -af $classes $class

    done

echo "===拷貝 注冊器類 FlutterPluginRegistrant==="
registrantFilePath=$framework_dir/Classes/flutter
cp -r ${framework_dir}/flutter/FlutterPluginRegistrant $registrantFilePath

echo "===移除臨時(shí)文件夾==="
rm -rf ${temp_dir}

將上述腳本文件放到module工程中并執(zhí)行。執(zhí)行完成后,會生成如圖以下產(chǎn)物:

編譯產(chǎn)物文件目錄

3、為編譯產(chǎn)物配置podspec文件

flutter_module.podspeccocoapods的配置文件,用于將產(chǎn)物集成到iOS項(xiàng)目中,配置如下:

Pod::Spec.new do |spec|

  spec.name         = "flutter_module"
  spec.version      = "0.0.1"
  spec.summary      = "A short description of flutter_module."

  spec.homepage     = "http://EXAMPLE/flutter_module"

  spec.license      = "MIT"
  # spec.license      = { :type => "MIT", :file => "FILE_LICENSE" }

  spec.author             = { "author" => "xxx@163.com" }
  spec.platform     = :ios
  spec.platform     = :ios, "8.0"

  spec.source       = { :git => "~/Documents/code/mycode/ManageLocalCode" }
# { :git => "http://EXAMPLE/flutter_module.git", :tag => "#{spec.version}" }

  spec.source_files  = "Classes", "Classes/flutter/**/*.{h,m}"
  spec.resources = "resources/**"
  spec.frameworks = "UIKit", "Foundation"
  spec.ios.vendored_frameworks = "App.framework", "Flutter.framework"
  spec.requires_arc = true

  #如果插件中有依賴三方庫,需要配置在這里,多個(gè)庫可以添加多個(gè)spec.dependency
# spec.dependency "xxx", "~> 0.1.3"

end
4、iOS原生項(xiàng)目集成Flutter產(chǎn)物

在iOS工程中的Podfile文件中添加以下代碼:

pod 'flutter_module', :path => "編譯產(chǎn)物路徑"

上述方式是采用本地路徑保存編譯產(chǎn)物,如有需要可以上傳到遠(yuǎn)程github或者自定義云倉庫。

5、注意事項(xiàng)

現(xiàn)在項(xiàng)目已經(jīng)集成好了Flutter運(yùn)行的所有編譯文件以及代碼文件。但是在執(zhí)行過程中依然有幾個(gè)需要注意的點(diǎn):

① 如果是由原來官方的集成方式改為產(chǎn)物集成方式時(shí),需要?jiǎng)h除原Build Phases中的以下腳本
"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh" build
"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh" embed

以及原Plugin文件在Header Search PathsOther linker Flags中的文件引用。

② 保證module工程/.ios 項(xiàng)目能運(yùn)行成功

運(yùn)行不成功的原因有可能是你的Podfile文件中未添加Plugin工程中的三方庫依賴。只有.ios工程中的項(xiàng)目能正常運(yùn)行,Flutter產(chǎn)物收集腳本中的flutter build ios命令才能執(zhí)行成功。

③ 每次的產(chǎn)物更新都需要在iOS工程中執(zhí)行pod install,更新產(chǎn)物文件的引用。
module工程中的.ios文件夾中的內(nèi)容被誤刪了如何恢復(fù)?

由于在寫收集Flutter產(chǎn)物腳本時(shí),我不小心有刪除過.ios文件夾下的內(nèi)容,在module工程中執(zhí)行以下代碼可以恢復(fù):

flutter create  -i objc .

四、參考文章

如何寫一個(gè)Flutter自動(dòng)打包成iOS代碼模塊的腳本
Flutter創(chuàng)建工程命令

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。