1. 程式人生 > >從零搭建 iOS Native Flutter 混合工程

從零搭建 iOS Native Flutter 混合工程

本文來實現一個靈活、無侵入、低耦合的 iOS Flutter 混合工程。 我們希望混合開發至少得保證如下特點:

  • 對Native工程無侵入
  • 對Native工程零耦合
  • 不影響Native工程的開發流程與打包流程
  • 易本地除錯

一、Flutter 提供的 Native Flutter 混合工程方式

Flutter 官方提供的混合工程搭建方法: Add Flutter to existing apps 文章中介紹瞭如何在現有 App 里加入Flutter,下面進行逐步介紹一下

1. 建立 Flutter 工程

請自行 百度/Google Flutter 安裝教程,安裝Flutter。然後到任意目錄下執行flutter create -t module my_flutter

,"my_flutter" 是要建立的 Flutter 工程的名稱。

2. 通過 Cocoapods 將 Flutter 引入 現有 Native 工程

在Podfile新增以下下程式碼

flutter_application_path = "xxx/xxx/my_flutter"
eval(File.read(File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb')), binding)
複製程式碼

然後執行pod install

3. 修改 Native 工程

開啟Xcode工程,選擇要加入 Flutter App 的 target,選擇 Build Phases

,點選頂部的 + 號,選擇 New Run Script Phase,然後輸入以下指令碼

"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh" build
"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh" embed
複製程式碼

二、分析 Native Flutter 混合工程

按照上面三個步驟進行逐一分析每一步的問題,並提供優化方案。

1. 建立 Flutter 工程

這一步首先在自己電腦上安裝 Flutter,然後使用 flutter create。這裡就存在問題,在團隊開發中每個人安裝的 Flutter 版本可能並不同,這樣會出現Dart層Api相容性或Flutter虛擬機器不一致等問題。 在團隊協作中一定要保證 Flutter 工程依賴相同的 Flutter SDK,所有需要一個工具在執行 flutter

指令時可以根據當前 Flutter 工程使用對應版本的 Flutter SDK,目前有一個名叫flutter_wrapper的工具,使用 flutterw 代替 flutter 指令,工具會自動將 Flutter SDK 放在當前 Flutter 工程目錄下,並執行當前工程中的 flutter 命令,這樣就不再依賴本地電腦安裝的 Flutter SDK。

flutter_wrapper使用:

  1. flutter create 建立 Flutter 工程,這裡使用的是本地的 Flutter SDK
  2. 進入 Flutter 工程目錄安裝 'flutter_wrapper',執行 sh -c "$(curl -fsSL https://raw.githubusercontent.com/passsy/flutter_wrapper/master/install.sh)"
  3. 此後在當前 Flutter 工程需要使用 flutter 命令的地方都使用 ./flutterw來代替

2. 通過 Cocoapods 將 Flutter 引入 現有 Native 工程

這一步在 Podfile 裡添加里一個 'podhelper.rb' ruby 指令碼,指令碼會在 pod install/update 時執行,指令碼主要做4點事情:

  1. 解析 'Generated.xcconfig' 檔案,獲取 Flutter 工程配置資訊,檔案在'my_flutter/.ios/Flutter/'目錄下,檔案中包含了 Flutter SDK 路徑、Flutter 工程路徑、Flutter 工程入口、編譯目錄等。
  2. 將 Flutter SDK 中的 Flutter.framework 通過 pod 新增到 Native 工程。
  3. 將 Flutter 工程依賴的外掛通過 pod 新增到 Native 工程,因為有些外掛有 Native 部分程式碼。
  4. 使用 post_install 這個 pod hooks 來關閉 Native 工程的 bitcode,並將 'Generated.xcconfig' 檔案加入 Native 工程。

這一步存在問題是,'podhelper.rb'指令碼是通過一個本地 Flutter 工程路徑'flutter_application_path'來讀取,在團隊協作中我們很難保證每個人的本地 Flutter 工程路徑都一樣,在同步程式碼時大家可能都要頻繁修改'flutter_application_path'變數,這樣很不友好。

解決這個問題的思路就是將 Flutter 工程放在當前 Native 工程的目錄下,我們可以再加入一個 ruby 指令碼,在每次執行 pod install/update 時,將 Flutter 工程從 git 上拉取一份放在當前目錄下,這樣 Flutter 工程的路徑就統一了。大致程式碼如下:

flutter_application_path = __dir__ + "/.flutter/app"
`git clone git://xxxx/my_flutter.git #{flutter_application_path}`
# 如果想要除錯本地的 Flutter 工程,就把下面這行註釋放開
# flutter_application_path = "xxx/xxx/my_flutter"
eval(File.read(File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb')), binding)
複製程式碼

上述程式碼只是臨時程式碼,為了演示將 Flutter 工程放在當前目錄下這個思路,後面會有完整的實現程式碼。

3. 修改 Native 工程

這裡執行了一個'xcode_backend.sh'指令碼的兩個命令build、embed,兩個命令分別的作業是:

  • build: 根據當前 Xcode 工程的 'configuration' 和其他編譯配置編譯 Flutter 工程,'configuration'通常為'debug'或者'release'
  • embed: 將 build 出來的 framework、資源包放入 Xcode 編譯目錄,並簽名 framework

這裡存在的問題是:Flutter 工程依賴 Native工程來執行編譯,並影響Native工程的開發流程與打包流程。

通常 'configuration' 裡不止有 'debug' 或者 'release',可能會有自定義的名稱,如果我們使用自定義的 'configuration' 編譯,那麼 xcode_backend.sh build 就會執行失敗。因為Flutter 編譯模式是通過 'configuration' 獲取的,Flutter 支援 Debug、Profil、Release 三種編譯模式,而我們自定義的名稱不在這三種之中,Flutter 就不知道該怎麼編譯。

每次 Native 編譯時 Flutter 就需要編譯,其實是產生了相互依賴:Flutter 編譯依賴 Native 編譯環境,Native 依賴 Flutter 編譯通過。

我們希望做到:Native 依賴 Flutter 編譯出來的產物,並且保留依賴 Flutter 原始碼進行除錯的能力。

實現這個目標我們需要兩部分:

  • 第一部分:Flutter 工程裡建立一個打包指令碼,可以一鍵產生 Flutter 工程產物;
  • 第二部分:在 Native 工程獲取 FLutter 工程的編譯產物,並通過 pod 新增到工程;並且保留依賴 Flutter 工程原始碼的功能。

三、實現 Native Flutter 混合工程

下面我們來實現上文提到的兩個部分

第一部分實現“打包指令碼”

這一部分我們需要實現指令碼自動打包 Flutter 工程,拆分一下這個指令碼流程,大致分為一下幾個步驟:

  1. 檢查 Flutter 環境,拉取 Flutter plugin
  2. 編譯 Flutter 工程產物
  3. 複製 Flutter 外掛中的 Native 程式碼
  4. 將產物同步到產物釋出的伺服器

下面來一步一步的分析並實現每一步:

(1) 檢查 Flutter 環境,拉取 Flutter plugin

這一步做的工作是檢查是否安裝了 'flutter_wrapper',如果安裝則進行安裝,然後執行 ./flutterw packages get,Shell程式碼如下:

flutter_get_packages() {
    echo "================================="
    echo "Start get flutter app plugin"

    local flutter_wrapper="./flutterw"
    if [ -e $flutter_wrapper ]; then
        echo 'flutterw installed' >/dev/null
    else
        bash -c "$(curl -fsSL https://raw.githubusercontent.com/passsy/flutter_wrapper/master/install.sh)"
        if [[ $? -ne 0 ]]; then
            # 上一步指令碼執行失敗
            echo "Failed to installed flutter_wrapper."
            exit -1
        fi
    fi

    ${flutter_wrapper} packages get --verbose
    if [[ $? -ne 0 ]]; then
        # 上一步指令碼執行失敗
        echo "Failed to install flutter plugins."
        exit -1
    fi

    echo "Finish get flutter app plugin"
}
複製程式碼

(2) 編譯 Flutter 工程產物

這一步是指令碼的核心,主要邏輯和上文中'xcode_backend.sh build'類似,大致程式碼如下:

# 預設debug編譯模式
BUILD_MODE="debug"
# 編譯的cpu平臺
ARCHS_ARM="arm64,armv7"
# Flutter SDK 路徑
FLUTTER_ROOT=".flutter"
# 編譯目錄
BUILD_PATH=".build_ios/${BUILD_MODE}"
# 存放產物的目錄
PRODUCT_PATH="${BUILD_PATH}/product"
# 編譯出的flutter framework 存放的目錄
PRODUCT_APP_PATH="${PRODUCT_PATH}/Flutter"

build_flutter_app() {
    echo "================================="
    echo "Start Build flutter app"
    # 建立目錄
    mkdir -p -- "${PRODUCT_APP_PATH}"
    # flutter 工程入口 dart檔案
    local target_path="lib/main.dart"
    # flutter sdk 目錄解析
    local artifact_variant="unknown"
    case "$BUILD_MODE" in
    release*)
        artifact_variant="ios-release"
        ;;
    profile*)
        artifact_variant="ios-profile"
        ;;
    debug*)
        artifact_variant="ios"
        ;;
    *)
        echo "ERROR: Unknown FLUTTER_BUILD_MODE: ${BUILD_MODE}."
        exit -1
        ;;
    esac

    if [[ "${BUILD_MODE}" != "debug" ]]; then
        # 非debug編譯模式
        # build fLutter app,output App.framework
        ${FLUTTER_ROOT}/bin/flutter --suppress-analytics \
            --verbose \
            build aot \
            --output-dir="${BUILD_PATH}" \
            --target-platform=ios \
            --target="${target_path}" \
            --${BUILD_MODE} \
            --ios-arch="${ARCHS_ARM}"

        if [[ $? -ne 0 ]]; then
            echo "Failed to build flutter app"
            exit -1
        fi
    else
        # debug編譯模式直接使用編譯好的App.framework,
        # 因為在 debug 模式下 flutter 程式碼並沒有編譯成二進位制機器碼,而是在後續build bundle時被打包進資源包,
        # 在'xcode_backend.sh'腳本里,這一步這裡只是編譯成一個App.framework空殼。
        # 提前編譯好的原因是'xcode_backend.sh'指令碼執行和Xcode一起執行,所以執行時能獲取到Xcode設定的編譯配置,能正確的編譯出'App.framework',
        # 而本指令碼不依賴Xcode執行,即便把'xcode_backend.sh'對應的程式碼拷貝出來也不能正確的編譯出'App.framework',除非我們能正確的配置編譯環境。
        # 
        # 而我又不想那麼麻煩,選擇另闢蹊徑:
        # 隨便建立了一個 Flutter 工程,
        # 在debug模式下,先在模擬器編譯執行一下,得到x86_64的App.framework,
        # 再到真機執行一下,得到arm64/armv7的App.framework,
        # 最後使用lipo命令將兩個App.framework合併,得到x86_64/arm64/armv7的App.framework,
        # 這樣最後得到的App.framework在模擬器和真機都可以用
        # 因為debug模式下App.framework就是佔位的空殼,所以其他flutter工程一樣用
        local app_framework_debug="iOSApp/Debug/App.framework"
        cp -r -- "${app_framework_debug}" "${BUILD_PATH}"
    fi

    # copy info.plist to App.framework
    app_plist_path=".ios/Flutter/AppFrameworkInfo.plist"
    cp -- "${app_plist_path}" "${BUILD_PATH}/App.framework/Info.plist"

    local framework_path="${FLUTTER_ROOT}/bin/cache/artifacts/engine/${artifact_variant}"
    local flutter_framework="${framework_path}/Flutter.framework"
    local flutter_podspec="${framework_path}/Flutter.podspec"

    # copy framework to PRODUCT_APP_PATH
    cp -r -- "${BUILD_PATH}/App.framework" "${PRODUCT_APP_PATH}"
    cp -r -- "${flutter_framework}" "${PRODUCT_APP_PATH}"
    cp -r -- "${flutter_podspec}" "${PRODUCT_APP_PATH}"

    local precompilation_flag=""
    if [[ "$BUILD_MODE" != "debug" ]]; then
        precompilation_flag="--precompiled"
    fi

    # build bundle
    ${FLUTTER_ROOT}/bin/flutter --suppress-analytics \
        --verbose \
        build bundle \
        --target-platform=ios \
        --target="${target_path}" \
        --${BUILD_MODE} \
        --depfile="${BUILD_PATH}/snapshot_blob.bin.d" \
        --asset-dir="${BUILD_PATH}/flutter_assets" \
        ${precompilation_flag}

    if [[ $? -ne 0 ]]; then
        echo "Failed to build flutter assets"
        exit -1
    fi

    # copy Assets
    local product_app_assets_path="${PRODUCT_APP_PATH}/Assets"
    mkdir -p -- "${product_app_assets_path}"
    cp -rf -- "${BUILD_PATH}/flutter_assets" "${PRODUCT_APP_PATH}/Assets"

    # setting podspec
    # replace:
    # 'Flutter.framework'
    # to:
    # 'Flutter.framework', 'App.framework'
    #   s.resource='Assets/*'
    sed -i '' -e $'s/\'Flutter.framework\'/\'Flutter.framework\', \'App.framework\'\\\n  s.resource=\'Assets\/*\'/g' ${PRODUCT_APP_PATH}/Flutter.podspec

    echo "Finish build flutter app"
}
複製程式碼

(3) 複製 Flutter 外掛中的 Native 程式碼

Flutter 使用的各種外掛可能會包含 Native 程式碼,並且這些程式碼已經提供了podspec,可以使用 pod 直接引入。我們要做的就是把外掛的 Native 程式碼拷貝到產物目錄。 Flutter 建立了一個給 Native 註冊外掛的 pod 庫 'FlutterPluginRegistrant',這個也需要拷貝出來, 在 Flutter 工程根目錄下有一個 .flutter-plugins 檔案,檔案內部記錄了外掛的名字和外掛的路徑,格式為 pugin_name=/xx/xx/xx,解析這個檔案就可以得到外掛資訊,程式碼如下:

flutter_copy_packages() {
    echo "================================="
    echo "Start copy flutter app plugin"

    local flutter_plugin_registrant="FlutterPluginRegistrant"
    local flutter_plugin_registrant_path=".ios/Flutter/${flutter_plugin_registrant}"
    echo "copy 'flutter_plugin_registrant' from '${flutter_plugin_registrant_path}' to '${PRODUCT_PATH}/${flutter_plugin_registrant}'"
    cp -rf -- "${flutter_plugin_registrant_path}" "${PRODUCT_PATH}/${flutter_plugin_registrant}"

    local flutter_plugin=".flutter-plugins"
    if [ -e $flutter_plugin ]; then
        OLD_IFS="$IFS"
        IFS="="
        cat ${flutter_plugin} | while read plugin; do
            local plugin_info=($plugin)
            local plugin_name=${plugin_info[0]}
            local plugin_path=${plugin_info[1]}

            if [ -e ${plugin_path} ]; then
                local plugin_path_ios="${plugin_path}ios"
                if [ -e ${plugin_path_ios} ]; then
                    if [ -s ${plugin_path_ios} ]; then
                        echo "copy plugin 'plugin_name' from '${plugin_path_ios}' to '${PRODUCT_PATH}/${plugin_name}'"
                        cp -rf ${plugin_path_ios} "${PRODUCT_PATH}/${plugin_name}"
                    fi
                fi
            fi
        done
        IFS="$OLD_IFS"
    fi

    echo "Finish copy flutter app plugin"
}
複製程式碼

(4) 將產物同步到保留產物的伺服器

經過上面的幾個步驟後會生成一個產物目錄,這個目錄下會有幾個二級目錄,每個二級目錄裡都包含一個 podspec 檔案。

也就是說這個產物目錄裡存放的就是 cocoapods 庫,將目錄拷貝到 Native 工程,然後用 pod 'pod_name', :path=>'xx/xxx' 的形式引用就可以了。

有了產物後我們需要一個存放產物的地方, 大家可以去這個地方下載,這一步比較靈活,可以選擇將產物放在git倉庫、http伺服器、ftp伺服器等。我最終選擇將產物壓縮成 zip 上傳到 Maven 上,原因是為了和 Android Flutter 產物放在一個地方,並且 Maven 已成做好的產物版本管理。

Maven上傳程式碼比較簡單,這裡不再贅述,有興趣可以到文末的github倉庫檢視程式碼。

Flutter 工程版本設定是在工程目錄下的 'pubspec.yaml' 檔案,打包指令碼讀取這個檔案來確定產物的版本。

最後這個指令碼使用方式為 ./build_ios.h -m debug ./build_ios.h -m release,上文中沒有提到的一點是隻有 release 模式編譯的包才會上傳的伺服器,debug 只是編譯到產物目錄。

第二步 Native 依賴 Flutter 產物

這部分我們需要實現獲取指定版本 Flutter 工程 release 產物,並整合到 Native 專案,並保留可以除錯 Flutter 工程的能力。

也是來拆分一下指令碼流程:

  • 獲取 Flutter 工程產物
    • 獲取 release 產物
    • 獲取 debug 產物
  • 通過 pod 引入 Flutter 工程產物

(1) 獲取 Flutter 工程產物

上文說到只有 release 產物放在了產物伺服器上,debug 只是編譯到產物目錄。不上傳 debug 的原因是,debug 階段就是開發階段,舉個不太恰當的例子:哪有開發階段就把包上傳 app store 的? 也就代表這 release 的產物和 debug 的產物獲取邏輯不一樣,並且我們的指令碼支援兩種方式的切換,所以在 Podfile 新增如下程式碼:

# 設定要引入的 flutter app 的版本
FLUTTER_APP_VERSION="1.1.1"

# 是否進行除錯 flutter app,
# 為true時FLUTTER_APP_VERSION配置失效,下面的三項配置生效
# 為false時FLUTTER_APP_VERSION配置生效,下面的三項配置失效
FLUTTER_DEBUG_APP=false

# Flutter App git地址,從git拉取的內容放在當前工程目錄下的.flutter/app目錄
# 如果指定了FLUTTER_APP_PATH,則此配置失效
FLUTTER_APP_URL="git:/xxxx.git"
# flutter git 分支,預設為master
# 如果指定了FLUTTER_APP_PATH,則此配置失效
FLUTTER_APP_BRANCH="master"

# flutter本地工程目錄,絕對路徑或者相對路徑,如果有值則git相關的配置無效
FLUTTER_APP_PATH="../my_flutter"

eval(File.read(File.join(__dir__, 'flutterhelper.rb')), binding)
複製程式碼

Podfile 其實就是 Ruby 程式碼,上面幾個由大寫字母組成的變數是全域性變數,最後一句程式碼的作用為讀取'flutterhelper.rb'裡的程式碼並執行,在'flutterhelper.rb'裡可以獲取到上面定義的全域性變數,根據這幾個變數做不同的操作,其中選擇使用 release 還是 debug 的程式碼如下:

if FLUTTER_DEBUG_APP.nil? || FLUTTER_DEBUG_APP == false
    # 使用 flutter release 模式
    puts "開始安裝 release mode flutter app"
    install_release_flutter_app()
else
    # 存在debug配置,使用 flutter debug 模式
    puts "開始安裝 debug mode flutter app"
    install_debug_flutter_app()
end
複製程式碼

install_release_flutter_app為操作 release 產物的函式,install_debug_flutter_app為操作 debug 產物的函式。

處理 release 模式主要就是獲取 release 產物,程式碼如下:

# 安裝正式環境環境app
def install_release_flutter_app
    if FLUTTER_APP_VERSION.nil?
        raise "Error: 請在 Podfile 裡設定要安裝的 Flutter app 版本 ,例如:FLUTTER_APP_VERSION='1.0.0'"
    else
        puts "當前安裝的 flutter app 版本為 #{FLUTTER_APP_VERSION}"
    end

    # 存放產物的目錄
    flutter_release_path = File.expand_path('.flutter_release')
    # 是否已經存在當前版本的產物
    has_version_file = true
    if !File.exist? flutter_release_path
        FileUtils.mkdir_p(flutter_release_path)
        has_version_file = false
    end

    # 存放當前版本產物的目錄
    flutter_release_version_path = File.join(flutter_release_path, FLUTTER_APP_VERSION)
    if !File.exist? flutter_release_version_path
        FileUtils.mkdir_p(flutter_release_version_path)
        has_version_file = false
    end

    # 產物包
    flutter_package = "flutter.zip"
    flutter_release_zip_file =  File.join(flutter_release_version_path, flutter_package)
    if !File.exist? flutter_release_zip_file
        has_version_file = false
    end

    # 產物包下載完成標誌
    flutter_package_downloaded = File.join(flutter_release_version_path, "download.ok")
    if !File.exist? flutter_package_downloaded
        has_version_file = false
    end

    if has_version_file == true
        # 解壓
        flutter_package_path = unzip_release_flutter_app(flutter_release_version_path, flutter_release_zip_file)
        # 開始安裝
        install_release_flutter_app_pod(flutter_package_path)
    else
        # 刪除老檔案
        FileUtils.rm_rf(flutter_release_zip_file)
        # 刪除標誌物
        FileUtils.rm_rf(flutter_package_downloaded)

        # 下載
        download_release_flutter_app(FLUTTER_APP_VERSION, flutter_release_zip_file, flutter_package_downloaded)
        # 解壓
        flutter_package_path = unzip_release_flutter_app(flutter_release_version_path, flutter_release_zip_file)
        # 開始安裝
        install_release_flutter_app_pod(flutter_package_path)
    end
end
複製程式碼

unzip_release_flutter_app為解壓zip格式產物的函式,download_release_flutter_app為從 Maven 下載產物的函式,這兩個比較簡單,詳細程式碼請看文末 github 倉庫。 install_release_flutter_app_pod為通過 pod 將產物新增到 Native 的函式,後面會詳細介紹。

處理 debug 模式的操作為,獲取 Flutter 工程原始碼,執行 build_ios.sh -m debug 進行打包,然後得到 debug 產物目錄,詳細程式碼如下:

# 安裝開發環境app
def install_debug_flutter_app

    puts "如果是第一次執行開發環境Flutter專案,此過程可能會較慢"
    puts "請耐心等️待☕️️️️️☕️☕️\n"
    
    # 預設Flutter App 目錄
    flutter_application_path = __dir__ + "/.flutter/app"
    flutter_application_url = ""
    flutter_application_branch = 'master'
    
    # 指定了FLUTTER_APP_PATH就用原生代碼,複製從git拉取
    if FLUTTER_APP_PATH != nil
        File.expand_path(FLUTTER_APP_PATH)
        if File.exist?(FLUTTER_APP_PATH) 
            flutter_application_path = FLUTTER_APP_PATH
        else
            flutter_application_path = File.expand_path(FLUTTER_APP_PATH)
            if !File.exist?(flutter_application_path) 
                raise "Error: #{FLUTTER_APP_PATH} 地址不存在!"
            end
        end
        
        puts "\nFlutter App路徑: "+flutter_application_path
    else
        if FLUTTER_APP_URL != nil
            flutter_application_url = FLUTTER_APP_URL
            if FLUTTER_APP_BRANCH != nil
                flutter_application_branch = FLUTTER_APP_BRANCH
            end
        else
            raise "Error: 請在'Podfile'裡增加Flutter App git地址配置,配置格式請檢視'flutterhelper.rb'檔案"
        end
        puts "\n拉取 Flutter App 程式碼"
        puts "Flutter App路徑: "+flutter_application_path
        update_flutter_app(flutter_application_path, flutter_application_url, flutter_application_branch)
    end

    puts "\n編譯 Flutter App"
    # PUB_HOSTED_URL FLUTTER_STORAGE_BASE_URL 為了加快速度,使用國內映象地址
    `export PUB_HOSTED_URL=https://pub.flutter-io.cn && \
    export FLUTTER_STORAGE_BASE_URL=https://storage.flutter-io.cn && \
    cd #{flutter_application_path} && \
    #{flutter_application_path}/build_ios.sh -m debug`

    if $?.to_i == 0
        flutter_package_path = "#{flutter_application_path}/.build_ios/debug/product"
        # 開始安裝
        install_release_flutter_app_pod(flutter_package_path)
    else
        raise "Error: 編譯 Flutter App失敗"
    end
end
複製程式碼

update_flutter_app為從 git 拉取程式碼的函式也不贅述,詳情見文末 github 倉庫。

(2) 通過 pod 引入 Flutter 工程產物

上文兩個函式執行完成後,就得到了產物的存放目錄,下面只需要引入到 Native 倉庫就可以了,也就是install_release_flutter_app_pod函式,從程式碼如下:

# 將 Flutter app 通過 pod 安裝
def install_release_flutter_app_pod(product_path)
    if product_path.nil?
        raise "Error: 無效的 flutter app 目錄"
    end

    puts "將 flutter app 通過 pod 匯入到 工程"

    Dir.foreach product_path do |sub|
        if sub.eql?('.') || sub.eql?('..') 
            next
        end

        sub_abs_path = File.join(product_path, sub)
        pod sub, :path=>sub_abs_path
    end

    post_install do |installer|
        installer.pods_project.targets.each do |target|
            target.build_configurations.each do |config|
                config.build_settings['ENABLE_BITCODE'] = 'NO'
            end
        end
    end
end 
複製程式碼

如果要修改 release 產物版本,則設定FLUTTER_APP_VERSION。 如果想要 debug flutter,則設定 FLUTTER_DEBUG_APP=true,如果除錯原生代碼則設定 FLUTTER_APP_PATH="../my_flutter",負責將 FLUTTER_APP_PATH 註釋掉,配置 FLUTTER_APP_URL FLUTTER_APP_BRANCH

四、總結

對照上文中提到的對混合工程的要求,總結一下:

  • Flutter 工程完全不依賴 Native 工程,而是通過 'build_ios.sh' 指令碼進行編譯打包;
  • 通過 pod 引入 Flutter 工程對 Native 也沒有浸入,不要在 Native 工程裡增加 Flutter 打包指令碼;
  • Native 開發工程師只需要執行 pod install 所有的 Flutter 依賴就都加入進工程,不需要工程師配置 Flutter 開發環境;也不影響 Native 打包;
  • 也保留了本地除錯 Flutter 工程的功能;

Github 倉庫:iOS_Flutter_Hybrid_Project