在上一篇# 在iOS项目中依赖Flutter Module-②远程依赖Git资源实现了将Flutter编译产物发布到远程Git仓库以及iOS端远程依赖Flutter编译产物的方案。但是这个方案有一个很大的问题,就是Flutter.xcframework文件太大,新版Flutter编译出来接近480M,如果随其它产物一起打上版本tag,每次发布到Git、从Git上拉取新版本都将耗费不少时间。由于我们并不需要改动Flutter Engine,所以每次编译导出的Flutter.xcframework都不会变,除非Flutter版本更新了。而App.xcframework和其它第三方插件库跟我们编写的Dart代码是关联的,每次编译都可能会变更。所以正常情况下,每次发布新版本产物,只需要发布除Flutter.xcframework以外的产物即可。
回顾# 在iOS项目中依赖Flutter Module-①本地依赖 - 章节3,可以选择编译导出Flutter.podspec,Flutter.podspec依赖了远程服务器上的Flutter.xcframework压缩文件,并且支持CDN加速,压缩后不到原文件的一半大小,首次下载下来也比拉取Git快。Flutter.podspec的版本号对应本机上的Flutter版本号,基本不会变化,CocoaPods根据版本号缓存资源,就不会反复下载更新。相比将Flutter.xcframework放到Git上,通过本地的Flutter.podspec中转依赖Flutter.xcframework.zip,不仅减少Git服务器的存储压力,还能减少不必要的时间损耗,能明显缩短整个流程的时间。
围绕本地podspec中转依赖远程Flutter,编译产物,我尝试了几种方案,最终我选择了本地podspec中转依赖文件服务器上的Flutter.xcframework.zip + 本地podspec中转依赖Git上的其它.xcframework,充分发挥远程zip文件的下载优势和Git版本管理的优势。
也可以自行搭建文件服务器,将其它的xcframework压缩成zip文件上传到文件服务器,像Flutter.podspec一样通过本地podspec中转依赖zip文件。但是不好做版本控制和版本回滚,文件服务器上面也会不断累计旧版本的编译产物,对于APP端开发者而言搭文件服务器也有点学习成本,整体而言我觉得没`远程zip & git混合依赖`的方案好用,后面就不做介绍了,如果感兴趣,就进传送门,可以找我要Node.js搭文件服务器的代码,网上找也可以。
如果想看更多的试验方案,请进传送门🚪。
准备阶段
为了方便测试代码,我把ios_module/flutter_module/andriod_module放在了一个工作区目录下。ios_module就是iOS项目所在目录,整体目录结构如下:
some/path/
├── andriod_module
│ ├── ...
├── flutter_module
│ ├── ...
├──ios_module
│ ├── ...
先创建一个Flutter Module,随便加点flutter代码和第三方组件(比如flutter_boost);
cd some/path/
flutter create --template module flutter_module
创建一个临时目录flutter_build/Frameworks/,用于存放编译导出的产物,用完即删,至此形成的目录结构如下;
some/path/
├── andriod_module
│ ├── ...
├── flutter_build
│ ├── Frameworks
├── flutter_module
│ ├── README.md
│ ├── build
│ ├── flutter_module.iml
│ ├── flutter_module_android.iml
│ ├── lib
│ ├── pubspec.lock
│ ├── pubspec.yaml
│ └── test
├──ios_module
├── FlutterBoostPro
├── FlutterBoostPro.xcodeproj
├── FlutterBoostPro.xcworkspace
├── Podfile
├── Podfile.lock
└── Pods
创建Git仓库FlutterModuleSDK.git,用来存放除Flutter.xcframework以外的编译产物,建好git之后克隆到本机(可以是工作区some/path/下)。
然后创建Git仓库FlutterModuleSDKPodspec.git,建好git之后克隆到本机(可以是工作区some/path/下),用来存放Flutter.podspec和FlutterModuleSDK.podspec。Flutter.podspec是编译导出的文件,FlutterModuleSDK.podspec需要我们自行创建,并指定依赖前面创建的FlutterModuleSDK.git,文件内容如下:
Pod::Spec.new do |s|
s.name = 'FlutterModuleSDK'
s.version = '1.0.1'
s.summary = 'Flutter Module SDK'
s.source = { :git => 'https://a.gitlab.cn/flutter/FlutterModuleSDK.git', :tag => "#{s.version}" }
s.platform = :ios, '8.0'
s.requires_arc = true
s.vendored_frameworks = '*.xcframework'
end
最后形成的目录结构如下
井号 # 代表目前不存在,编译后才会有,先占个位而已
some/path/
├── andriod_module
│ ├── ...
├── flutter_build
│ ├── Frameworks
│ │ ├── # Debug
│ │ ├── # Profile
│ │ ├── # Release
├── flutter_module
│ ├── README.md
│ ├── build
│ ├── flutter_module.iml
│ ├── flutter_module_android.iml
│ ├── lib
│ ├── pubspec.lock
│ ├── pubspec.yaml
│ └── test
├──ios_module
│ ├── FlutterBoostPro
│ ├── FlutterBoostPro.xcodeproj
│ ├── FlutterBoostPro.xcworkspace
│ ├── Podfile
│ ├── Podfile.lock
│ └── Pods
├──FlutterModuleSDK
│ ├── # App.xcframework、FlutterPluginRegistrant.xcframework和其它.xcframework
├──FlutterModuleSDKPodspec
│ ├── FlutterModuleSDK.podspec
│ ├── # Flutter.podspec
编译
先编译flutter_module,指定导出目录是flutter_build/Frameworks/,output的参数是相对路径。编译失败的话要么是Dart代码问题,要么是.ios/Runner.xcworkspace的开发证书没配置好。
cd flutter_module/
flutter build ios-framework --cocoapods --xcframework --no-universal --output=../flutter_build/Frameworks/
编译成功后,flutter_build/Frameworks/目录下面会新增以下文件夹。
├── flutter_build
│ ├── Frameworks
│ │ ├── Debug
│ │ ├── Profile
│ │ ├── Release
处理产物
将App.xcframework、FlutterPluginRegistrant.xcframework和其它.xcframework移到FlutterModuleSDK.git本地目录下;
├──FlutterModuleSDK
│ ├── App.xcframework
│ ├── FlutterPluginRegistrant.xcframework
│ ├── 其它.xcframework,比如flutter_boost.xcframework
将Flutter.podspec移到FlutterModuleSDKPodspec.git本地目录下(并不需要修改这个文件,直接用);
├──FlutterModuleSDKPodspec
│ ├── FlutterModuleSDK.podspec
│ ├── Flutter.podspec
修改FlutterModuleSDK.podspec中的版本号;
发布产物
提交FlutterModuleSDK.git中的更新,打上版本tag,push到远程仓库;
提交FlutterModuleSDKPodspec.git中的更新,push到远程仓库;
清空删除flutter_build/Frameworks/;
发布版本更新通知(钉钉、微信、邮件);
iOS端拉取更新
同步刚刚发布的*.podspec到iOS开发机上,第一次需要克隆FlutterModuleSDKPodspec.git到本地,后面只需要拉取更新FlutterModuleSDKPodspec.git即可;
在Podfile中添加本地依赖,:podspec指定FlutterModuleSDKPodspec.git中对应的.podspec文件,并使用相对路径;
pod 'Flutter', :podspec => './../FlutterModuleSDKPodspec/Flutter.podspec'
pod 'FlutterModuleSDK', :podspec => './../FlutterModuleSDKPodspec/FlutterModuleSDK.podspec'
pod update or install,运行代码测试;
尽量在.gitignore文件中忽略掉编译期间导入的.xcframework,不然一并提交到Git会很慢,还明显增加Git仓库的文件大小。
由于Flutter.podspec的版本号基本不会变,只有第一次下载zip文件会耗费点时间,CocoaPods缓存以后执行更新都会非常快。而FlutterModuleSDK.git里面的文件并不大,在内网Git的话每次更新都会比较快。所以相比将Flutter.xcframework放到Git上,通过本地的Flutter.podspec中转依赖Flutter.xcframework.zip,不仅减少Git服务器的存储压力,还能减少不必要的时间损耗,能明显缩短整个流程的时间。 另外这种方案也不用iOS同事安装Flutter开发环境,依赖Flutter侧代码就跟依赖第三方库一样,由于*.xcframework已经编译过了,编译iOS项目时不需要再编译Flutter代码,相比本地依赖就要快得多。
但这个方案也有个短板,如果iOS团队人多的话,要保证所有人在Podfile中填的:podspec相对路径都是一样的,也就是要求在每台开发机上,本地FlutterModuleSDKPodspec.git相对本地ios_module.git的路径是一致的。如果跟其他人填的相对路径不一致,提交到Git就会发生冲突。当然也可以禁止大家在克隆git时重命名本地仓库名,并且把FlutterModuleSDKPodspec.git、ios_module.git在同一个目录下,只是时间一久,难免会遗忘这个规则,另外还得跟新人强调这个规则。
改良
前面说了要保持相对路径一致,而FlutterModuleSDKPodspec.git和ios_module.git是纯人为建立并维护的关联关系,时间久了容易遗忘这个共识,所以需要更强烈的绑定关系来解决这个隐患。我找到的方案是借助git submodule将FlutterModuleSDKPodspec.git"内嵌"到ios_module.git中,成为子模块,还能让FlutterModuleSDKPodspec.git保持独立的Git仓库。
关于
git submodule的介绍可以看某大佬整理的《Git中submodule的使用》。
所以只需要修改iOS端拉取更新流程,在ios_module.git中增加git submodule,解决相对路径难以保持一致的问题,前面的编译、发布流程都不用改,FlutterModuleSDKPodspec.git还是独立的仓库。
iOS端拉取更新-改良版
首先在ios_module.git中添加子模块FlutterModuleSDKPodspec.git;
cd some/path/ios_module
git submodule add 'https://a.gitlab.cn/flutter/FlutterModuleSDKPodspec.git'
添加子模块后,主要会遇到两种情况,首先是新同事首次克隆ios_module.git,然后是后续的子模块更新。
- 如果是新同事首次克隆
ios_module.git,可能会遇到2种情况。- 1.常规的克隆指令,可能指定分支,但没有拉取
submodule子模块,就需要额外执行指令去递归拉取更新子模块。
git clone -b a_branch https://a.gitlab.cn/ios/ios_module.git git submodule update --init --recursive- 2.克隆的同时指定递归拉取更新子模块,则需要在指令中加上
--recurse-submodules
git clone -b a_branch https://a.gitlab.cn/ios/ios_module.git --recurse-submodules - 1.常规的克隆指令,可能指定分支,但没有拉取
FlutterModuleSDKPodspec.git发布更新后,需要在ios_module.git中更新本地子模块,加上foreach可以帮助我们遍历所有的子模块。
git submodule foreach 'git pull origin master'
如果更新了子模块(包括添加子模块),都会在ios_module.git产生modify,所以还需要额外提交更新到ios_module.git。
git status
# 下面2步可以在SourceTree之类的客户端操作,是可以看到submodule的
git add -A && git commit -m "添加或更新子模块 FlutterModuleSDKPodspec.git"
git push origin --tags && git push origin master
然后在Podfile中添加本地依赖,:podspec指定FlutterModuleSDKPodspec.git中对应的.podspec文件,并使用子模块的相对路径;
pod 'Flutter', :podspec => './FlutterModuleSDKPodspec/Flutter.podspec'
pod 'FlutterModuleSDK', :podspec => './FlutterModuleSDKPodspec/FlutterModuleSDK.podspec'
pod update or install,运行代码测试;
尽量在.gitignore文件中忽略掉编译期间导入的.xcframework,不然一并提交到Git会很慢,还明显增加Git仓库的文件大小。
综上所述,改良版就是把更新依赖的FlutterModuleSDKPodspec.git改成了更新子模块,而FlutterModuleSDKPodspec.git以子模块嵌入ios_module.git后路径是固定的,这样就能保住每台电脑上这2个git的相对路径是始终一致的,不用担心人为因素导致路径不一致的问题。
至此就实现了在iOS项目中远程依赖Flutter Module编译产物,特别感谢闲鱼团队分享的《Flutter in action》,远程依赖的思路也是从中获取的,只是不知道闲鱼团队具体是怎么实现的,所以花了不少时间在找合适的方案,试了几种方案,有可行的也有行不通的方案,最后选择了Git子模块 + 本地podspec中转远程zip & git的方案,具体的特点就是下面这些。总体而言,这个方案充分发挥了远程zip文件的下载优势和Git版本管理的优势,适合大小团队使用,技术难度低,关键能为iOS端节省很多编译时间,这一点特别实用。
- 将*.xcframework发布到FlutterModuleSDK.git中
- 将*.podspec发布FlutterModuleSDKPodspec.git中
- 将FlutterModuleSDKPodspec.git 以子模块的形式内嵌到 ios_module.git中
- 通过子模块的Flutter.podspec中转依赖远程服务器上的zip文件
- 通过子模块的FlutterModuleSDK.podspec中转依赖远程Git上的*.xcframework