背景
項目處于混合開發狀態,native開發的同學沒有裝flutter環境,無法編譯flutter的代碼,工程無法跑起來。
官方推薦方案
源碼依賴
flutter_application_path = './my_flutter'
load File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb')
target 'MyApp' do
install_all_flutter_pods(flutter_application_path)
end
Flutter工程以submodule方式引入native工程
優點
源碼依賴,方便調試,分支管理方便。
缺點
沒有flutter環境的同學無法編譯運行工程,對native侵入性比較強。
產物依賴
流程圖
iOS工程持續集成產物依賴
優點
侵入性低,產物依賴提升打包速度
缺點
開發迭代比較麻煩,打包產物步驟麻煩,生成產物需要托管管理,產物體積比較大
我的方案
APP.xcframework和Flutter.xcframework是以產物依賴,其他的插件是以源碼形式依賴
產物和源碼混合依賴
流程圖
iOS持續集成依賴
優點
只把dart代碼編譯成產物,其他使用源碼方式依賴,產物體積很小,侵入性低,打包速度快。
缺點
開發調試比較麻煩,需要托管產物,要花時間實現一套全網沒有參考的新方案
比較
方案實現
制作
打包產物腳本
打包app.xcframework產物,然后把flutter.podspec、FlutterPluginRegistrant、plugins復制到binary目錄,壓縮binary.zip目錄
#環境變量
function exportFlutterEnv() {
export PUB_HOSTED_URL=https://pub.flutter-io.cn
export FLUTTER_STORAGE_BASE_URL=https://storage.flutter-io.cn
}
#打包app.xcframework
function buildiOSFramework() {
if [ $BUILD_MODE == "Debug" ]
then
$FLUTTER_PATH/bin/flutter build ios-framework --no-release --no-profile --output=./ios-framework --verbose --cocoapods --no-tree-shake-icons
else
$FLUTTER_PATH/bin/flutter build ios-framework --no-debug --no-profile --output=./ios-framework --verbose --cocoapods --no-tree-shake-icons
fi
}
#創建目錄binary
function createBinaryDir() {
mkdir -v binary
}
#移動plugins
function movePluginsDir() {
cp -r -v .ios/.symlinks/plugins/. binary/plugins
}
#移動 app.xcframework、flutter.podspec、FlutterPluginRegistrant
function moveFlutterDir() {
mkdir -p binary/flutter/FlutterPluginRegistrant
cp -r -v .ios/Flutter/FlutterPluginRegistrant binary/flutter
cp -v ios-framework/$BUILD_MODE/Flutter.podspec binary/flutter/Flutter.podspec
cp -r -v ios-framework/$BUILD_MODE/App.xcframework/. binary/flutter/App.xcframework
}
#創建App.podspec
function createAppPodspec() {
touch binary/flutter/App.podspec
echo """Pod::Spec.new do |s|
s.name = 'App'
s.version = '1.0.0'
s.summary = 'fast apps.'
s.description = <<-DESC
Business Code
DESC
s.homepage = 'https://flutter.cn'
s.license = { :type => 'BSD' }
s.author = { 'Jacky' => 'shanhaoqiang@lizhi.fm' }
s.source = { :path => '.' }
s.documentation_url = 'https://flutter.cn/docs'
s.platform = :ios, '9.0'
s.vendored_frameworks = 'App.xcframework'
end
""">binary/flutter/App.podspec
}
#打zip包
function zipBinaryDir() {
zip -r binary-$BUILD_MODE.zip binary
}
#執行
exportFlutterEnv
buildiOSFramework
createBinaryDir
movePluginsDir
moveFlutterDir
createAppPodspec
zipBinaryDir
jenkins任務
拉取flutter工程代碼,執行上面的shell腳本,生成binary.zip,歸檔zip到jenkins的artifacts目錄中,獲取zip鏈接env.BUILD_URL + "artifact/binary-${BUILD_MODE}.zip"
stage('\u261D 使用分支名稱作為任務名稱') {
currentBuild.displayName = "#${BUILD_NUMBER}_${BRANCH_NAME}"
}
stage('\u262D 拉取代碼') {
checkout([$class: 'GitSCM', branches: [[name: '*/' + BRANCH_NAME]], doGenerateSubmoduleConfigurations: false, extensions: [[$class: 'CleanBeforeCheckout']], submoduleCfg: [], userRemoteConfigs: [[credentialsId: '25b61d91-a240-4eaa-8390-9ae2655fb969', refspec: '+refs/heads/' + BRANCH_NAME + ':refs/remotes/origin/' + BRANCH_NAME, url: GIT_URL]]])
withCredentials([sshUserPrivateKey(credentialsId: '25b61d91-a240-4eaa-8390-9ae2655fb969', keyFileVariable: 'SSH_KEY')]) {
sh """
git checkout ${BRANCH_NAME}
git pull origin ${BRANCH_NAME}
"""
}
}
stage('\u261D 拉取腳本') {
sh 'rm -rf ./' + 'flutterbinary'
sh 'git clone ' + 'git@gitlab.xx.fm:ocean/xx.git' + ' -b master --depth=1 ./' + 'flutterbinary'
}
stage('\u2615 編譯產物') {
sh """
cp flutterbinary/build.sh build.sh
sh build.sh ${BUILD_MODE} ${BUILD_NUMBER} ${BUILD_MODEL} ${FLUTTER_PATH} ${JDK_PATH}
"""
}
stage('\u26B0 保存成品') {
archiveArtifacts artifacts: "binary-${BUILD_MODE}.zip", fingerprint: true
}
stage('\u2709 發送通知') {
//iOS產物地址
url = env.BUILD_URL + "artifact/binary-${BUILD_MODE}.zip"
wrap([$class: 'BuildUser']) {
USER_ID = BUILD_USER_ID
USER_NAME = BUILD_USER
}
def updateLog = "${env.UPDATELOG}".trim()
String content = "請相關同事知悉。本次Flutter產物發布信息如下:\\n 操作人:${USER_NAME}" + "\\n 打包類型:${BUILD_MODE}" + "\\n 任務名:${env.JOB_NAME}" + "\\nFlutter iOS產物地址:${url}"+ "\\nFlutter Android aar包地址:${aarUrl}"+"\\n對應分支:${BRANCH_NAME}\\nFlutter地址:${GIT_URL}\\n更新內容:${updateLog.replace("\n", "\\n")}\\n"
def contentall = """
{"content":{"text": "${content}"},"msg_type":"text"}
"""
println("contentall:" + contentall)
def command = """
curl -X POST -H "Content-Type: application/json"\
-d '${contentall}' \
"https://open.feishu.cn/open-apis/bot/v2/hook/${env.NOTIFY_KEY}"
"""
sh(script: command)
}
集成
一行代碼集成flutter,Podfile中填寫jenkins打包的flutter項目的zip鏈接
def pod_flutter
puts "=== 集成flutter sdk ==="
install_remote_flutter_binary('https://jksclient.xx.fm/job/%E8%8D%94%E6%9E%9D-flutter/106/artifact/binary-Release.zip')
end
編寫Podfile插件
- 收到傳進來的url,對url做md5,創建Flutter緩存目錄,下載zip包,解壓
- 安裝flutter引擎,flutter指向podspec,podspec的source zip是官方地址
- 安裝plugins,各個plugin包含FlutterPluginRegistrant,指向解壓后端path地址
- 安裝App.xcframework,指向App所在的path路徑
## author:Jacky
## desc:install binary pods in Podfile
#!/usr/bin/env ruby
require 'digest'
require 'fileutils'
require 'uri'
require 'net/http'
require 'net/https'
module Pod
class Podfile
module DSL
#下載
def install_remote_flutter_binary(url = nil)
#md5
md5 = Digest::MD5.new # =>#<Digest::MD5>
md5 << url
md5value = md5.hexdigest # => "78e73102..."
flutter_f_home = Dir.home+'/Library/Caches/CocoaPods/Flutter/'
flutter_binary_home = flutter_f_home+md5value
flutter_binary_path = flutter_binary_home+'/binary'
#創建目錄
FileUtils.mkdir_p(flutter_binary_home)
#清除超過30天的緩存
xxxx
#判斷緩存
if File::directory?(flutter_binary_path) == false
#下載
puts "開始下載 "+url
#集成
install_all_lzflutter_pods(flutter_binary_path)
end
#安裝
def install_all_lzflutter_pods(flutter_binary_path)
install_lzflutter_engine_pod(flutter_binary_path)
install_lzflutter_plugin_pods(flutter_binary_path)
install_lzflutter_application_pod(flutter_binary_path)
end
# 安裝flutter引擎
def install_lzflutter_engine_pod(flutter_binary_path)
xxx
end
# Install Flutter plugin pods.
def install_lzflutter_plugin_pods(flutter_binary_path)
# Keep pod path relative so it can be checked into Podfile.lock.
# Process will be run from project directory.
#FlutterPluginRegistrant
xxx
#插件目錄
xxx
#plugins遍歷
xxx
end
# Install Flutter application pod.
def install_lzflutter_application_pod(flutter_binary_path)
xxx
end
end
end
end