目录
不管用何种方式在iOS项目依赖Flutter组件/将Flutter添加到现有的iOS项目,都需要使用flutter_module。所以我们先创建一个Flutter模块/组件。
cd some/path/
flutter create --template module flutter_module
flutter_module
目录的结构如下,.../flutter_module/lib/
里面就是我们写的dart代码文件。
├── flutter_module
│ ├── README.md
│ ├── build
│ ├── flutter_module.iml
│ ├── flutter_module_android.iml
│ ├── lib
│ ├── pubspec.lock
│ ├── pubspec.yaml
│ └── test
先cd
到some/path/flutter_module/
,用于执行Flutter指令。
建好flutter_module
后,随便加点flutter代码和第三方组件,就可以测试添加到iOS项目了。下面我们来尝试几种导入/依赖方案,前3种是官方推荐的,Flutter也有相关的开发文档 Adding Flutter to iOS
。
这种接入方式是最常见的一种,方便入手,代码也方便拆分,ios_module
/flutter_module
/andriod_module
可以放到不同的Git仓库,依赖时填写好相对的目录即可。为了方便测试代码,我把ios_module
/flutter_module
/andriod_module
放在了一个Git仓库/目录下。ios_module
就是iOS项目所在目录,整体目录结构如下:
├── andriod_module
│ ├── ...
├── 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
然后在iOS项目的Podfile文件中增加以下代码,借助flutter的podhelper.rb脚本编译Flutter组件导入到Pods中。这种方式无论是Debug运行还是Release打包,都行得通,也方便单人开发调试两端,在1台电脑用2个IDE开发调试两端代码即可;模拟器也能正常运行。但也有明显的缺陷,需要所有的iOS开发人员都安装有Flutter开发环境,另外iOS项目编译慢,每天编译的时间损耗还是不小的,打包时间也会增加不少。
flutter_application_path = '../flutter_module/'
load File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb')
install_all_flutter_pods(flutter_application_path)
首先需要将FlutterModule编译成iOS的.xcframwork
动态库,使用的是flutter build ios-framework --xcframework
指令集。不过这个指令可以设置导出的目录,所以我们可以直接导出到ios_module/
里,完整的目录结构如下,相比方案1,这里只增加了FlutterFrameworks
目录,专门用来存放Flutter的编译产物xcframework
。
├── andriod_module
│ ├── ...
├── 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
├── FlutterFrameworks
├── Podfile
├── Podfile.lock
└── Pods
执行这段完整的编译指令,即可导出Debug
/Profile
/Release
3种不同模式的xcframework
,并存放在这3个目录中。
这个过程会耗时1-2分钟
flutter build ios-framework --xcframework --no-universal --output=../ios_module/FlutterFrameworks/
然后在Xcode项目的跟目录右键添加文件,即Add file to 'FlutterBoostPro'
,选择create groups
,记得勾选Add to targets
。添加好了后,xcode会自动把这些xcframework
文件添加到Build Phases
的Link Binary With Libraries
中。这个过程在Flutter文档说明中是手动拖的,添加文件就省去拖文件的操作了。
将framework
添加到Embed Frameworks
中,但是初次添加时是找不到Embed Frameworks
的,所以要到Targets
- General
下面的 Frameworks, Libraries, and Embedded Content
一栏操作,不过我们使用Add file to 'a project'
添加的文件会自动加到这一栏,不用重复拖入文件。
在Build Settings
里面设置Runpath Search Paths
,添加"$(SRCROOT)/FlutterFrameworks/Release"
,指定相对路径。
这个时候试着运行项目,会出现报错:
dyld: Library not loaded: @rpath/Flutter.framework/Flutter
Referenced from: /private/var/containers/Bundle/Application/0A64CC78-D8D3-433C-B794-B8F928525885/FlutterBoostPro.app/FlutterBoostPro
Reason: image not found
dyld: launch, loading dependent libraries
DYLD_LIBRARY_PATH=/usr/lib/system/introspection
DYLD_INSERT_LIBRARIES=/Developer/usr/lib/libBacktraceRecording.dylib:/Developer/usr/lib/libMainThreadChecker.dylib:/Developer/Library/PrivateFrameworks/DTDDISupport.framework/libViewDebuggerSupport.dylib
是因为我们没有设置Embed & Sign
,状态是Do Not Embed
,Flutter文档说明中也指明了这个操作,都选择Embed & Sign
即可,设置正确后就能正常运行项目了。
这种导入framework
的方式,增加了编译Flutter、设置Target配置流程,如果需要切换Debug/Release
环境,还需要重新添加framework
,并重新设置FRAMEWORK_SEARCH_PATHS
和Embed & Sign
,在调试期间会增加不少的手动操作,当然为了方便调试,在flutter_module/.ios/
下面的Runner项目中也可以依赖iOS的业务代码,也可以快速调试,只是Flutter clean
后又要重新依赖,相对来说还是有点繁琐的;另外由于把编译产物直接导入到了iOS项目目录中,而Flutter.xcframework
文件很大,会直接增加git的文件大小,影响git push和pull,每次编译也会影响到其他人员分支的同步。但这种导入framework
的方式也有个非常大的优点,编译运行iOS项目耗时短,因为已经是编译过的xcframework
文件,不用每次附加编译Flutter代码,相比之下能节省很多编译时间;另外其他的开发人员也不用安装Flutter开发环境,直接跑iOS项目就行。
- 模拟器上运行不能正常展示Flutter页面,是空白的,待排查原因
前面2种方法都是依赖本机的编译产物,如果想把FlutterFrameworks
分享给同事,直接推到Git是行不通的,Flutter.xcframework
太大,超过了Github单个文件100M的限制,为此Flutter官方特意给Flutter.xcframework
实现了远程依赖。这种依赖Flutter组件的方法逻辑上跟方案2一致,先把flutter_module编译成framwork,存放在FlutterFrameworks
目录,再手动导入项目。区别在于Flutter.xcframework
是通过cocoaPods导入,直接依赖了Google的远程文件,这样就避免了git无法提交的问题。
首先还是编译Flutter,需要注意这里增加了--cocoapods
,编译后的产物包含了一个Flutter.podspec
,这个Flutter.podspec
依赖指向了Flutter.xcframework
的远程文件。
flutter build ios-framework --cocoapods --xcframework --no-universal --output=../ios_module/FlutterFrameworks/
我们可以看下Flutter.podspec
里面的具体内容,Flutter.xcframework
是线上拉取的,并不是我们前面用指令编译出来的,而且编译导出的目录里面也没有Flutter.xcframework
(编译后就删除了),只有App.xcframework
、FlutterPluginRegistrant.xcframework
和第三方库 flutter_boost.xcframework
。
Pod::Spec.new do |s|
s.name = 'Flutter'
s.version = '2.0.300' # 2.0.3
s.summary = 'Flutter Engine Framework'
s.description = <<-DESC
... 一些描述
DESC
s.homepage = 'https://flutter.dev'
s.license = { :type => 'MIT', :text => <<-LICENSE
... 一些版权声明
LICENSE
}
s.author = { 'Flutter Dev Team' => 'flutter-dev@googlegroups.com' }
s.source = { :http => 'https://storage.flutter-io.cn/flutter_infra/flutter/3459eb24361807fb186953a864cf890fa8e9d26a/ios-release/artifacts.zip' }
s.documentation_url = 'https://flutter.dev/docs'
s.platform = :ios, '8.0'
s.vendored_frameworks = 'Flutter.xcframework'
end
然后我们在Podfile
新增依赖Flutter
,执行pod install or update
pod 'Flutter', :podspec => './FlutterFrameworks/Release/Flutter.podspec'
首次安装会从云端下载Flutter.xcframework
,文件大小在200M左右 (各版本Flutter对应的大小不一样),有点考验网络,解压后的Flutter.xcframework
大小在480M左右,超出了Github的文件大小限制,所以务必要添加到.gitignore
中。
-> Installing Flutter (2.0.300)
> Http download
$ /usr/bin/curl -f -L -o /var/folders/jp/4slqd1n915b7s_dm47l0mk240000gn/T/d20210706-71545-f6gido/file.zip https://storage.flutter-io.cn/flutter_infra/flutter/3459eb24361807fb186953a864cf890fa8e9d26a/ios-release/artifacts.zip
--create-dirs --netrc-optional --retry 2 -A 'CocoaPods/1.10.1 cocoapods-downloader/1.4.0'
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 194M 100 194M 0 0 10.0M 0 0:00:19 0:00:19 --:--:-- 10.8M
如果这个时候我们运行项目,是会报错的,因为目前为止只依赖Flutter.xcframework
,其它的编译产物还是没有导入。所以我们还需要按照方案2的流程把App.xcframework
、FlutterPluginRegistrant.xcframework
和第三方库 flutter_boost.xcframework
导入到项目中。不过这里我不使用Add Files to 'a project'
来添加文件了,而是把这3个文件拖到Frameworks, Libraries, and Embedded Content
里面,设置Embed & Sign
,然后在Build Settings
的Runpath Search Paths
添加"$(SRCROOT)/FlutterFrameworks/Release"
,就可以正常运行项目了。
相比方案2,Flutter.xcframework
采用了CocoaPods依赖导入,但是其它的.xcframework
还是要手动导入。所以它的优缺点和方案2是基本一致的。另外在编译过程中可以看到生成了Flutter.xcframework
,但是并没有发现上传文件,所以Flutter.xcframework
是远程的静态资源,如果有自定义引擎需求,就得在方案2的基础上改了。
- 模拟器上运行不能正常展示Flutter页面,是空白的,待排查原因
在方案3 使用CocoaPods远程依赖Flutter.xcframework
中提到,Flutter.xcframework
是远程依赖的,那同样也可以远程依赖App.xcframework
、FlutterPluginRegistrant.xcframework
和其它第三方库 比如 flutter_boost.xcframework
,网上也有现成的实现方案和脚本(几乎都是旧版本的,需要自己改改)。
这个方案基于方案2或方案3来实现,开头都是先编译,然后收集编译产物,不过这里跟前面的方案不一样,要把收集到的编译产物推送到单独的私有Git仓库,并打上标签。
之所以说可以基于方案2或方案3来实现,是因为如果不需要自定义Flutter Engine,使用各个版本默认的
Flutter.fromework
,那就选择方案3的指令,而且方案3指令编译出来的framework都已经设置了Embed
,Flutter.fromework
使用从Google的云端下载下来的文件。如果有自定义引擎的需求,则需要把编译过的Flutter.fromework
也上传到内网私有git,选择方案2的指令,只是这个文件很大,会增加不少时间。不过还是建议给自定义的Flutter.fromework
单独建立git仓库以及版本控制,避免经常下载,浪费时间。
我们用方案3的指令编译flutter module;--outpu
输出路径最好指向编译产物所在Git路径,JKFlutter
就是我专门为编译产物建的私有git。
注意:Github有文件大小限制,flutter.framework目前已将近480M,超出了文件大小限制,所以是提交不上去的,我用Github(JKFlutter)只是为了方便介绍流程,实际Push到git时是传不上去的。这里建议换成内网的Gitlab仓库,不要用Github
flutter build ios-framework --cocoapods --xcframework --no-universal --output=../../JKFlutter/
为了方便测试,我把Flutter.fromework (下载的文件)
、App.xcframework
、FlutterPluginRegistrant.xcframework
和其它第三方库 比如 flutter_boost.xcframework
放到了同一个git目录,并且合并成了一个库,即FlutterModuleSDK
。所以需要额外创建一个描述文件FlutterModuleSDK.podspec
。podspec
的主要内容如下,重点则在s.vendored_frameworks = '*.xcframework'
,指向了JKFlutter.git
里的所有.xcframework
动态库。
Flutter.podspec
Pod::Spec.new do |s|
s.name = 'FlutterModuleSDK'
s.version = '1.0.2'
s.summary = 'Flutter Module SDK'
s.source = { :git => 'https://github.com/XiFengLang/JKFlutter.git', :tag => "#{s.version}" }
s.platform = :ios, '8.0'
s.requires_arc = true
s.vendored_frameworks = '*.xcframework'
end
然后提交git,并打上对应的标签,推到远程仓库JKFlutter.git
,到这里就完成推到云端的操作了,不用执行CocoaPods组件的发布指令。
这一步就轮到iOS端操作了,在iOS项目的Podfile中,增加FlutterModuleSDK
的依赖,执行pod install or update
,即可运行项目了。
platform :ios,'11.0'
source 'https://github.com/CocoaPods/Specs.git'
source 'https://github.com/XiFengLang/JKFlutter.git'
target 'FlutterBoostPro' do
# flutter_application_path = '../flutter_module/'
# load File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb')
# install_all_flutter_pods(flutter_application_path)
# pod 'Flutter', :podspec => './FlutterFrameworks/Release/Flutter.podspec'
pod 'FlutterModuleSDK', :git => 'https://github.com/XiFengLang/JKFlutter.git'
end
这个方案主要就3步,[1.编译]:编译FlutterModule,[2.发布] 收集产物推到云端Git,[3.更新代码] iOS端更新CocoaPods,就此简单实现了FlutterModule远程依赖。
但是细想下,正常情况下,我们改了Dart代码,不加新的第三方组件的话,编译后变的只有App.framework
,而FlutterPluginRegistrant.xcframework
和其它第三方库 比如 flutter_boost.xcframework
是不变的,所以我们也可以参考Flutter.fromework
的思路,给FlutterPluginRegistrant.xcframework
和其它第三方库 比如 flutter_boost.xcframework
再单独建个仓库,通常不用更新,除非第三方组件库的版本换了 或者 增加了新的第三方组件。多数情况只要维护App.framework
的更新就行,这些想法我会在下一节测试。
优点:所有Flutter编译出来的framework都放到了git,方便统一的版本管理,Flutter开发基本可以和iOS开发相互独立,也不用所有iOS开发人员都安装Flutter开发环境,也避免了iOS侧因Flutter版本不一致导致的问题。
缺点:Flutter.framework文件太大,没有压缩,上传到git / 从git克隆下载下来很费时。如果Git有单个文件大小限制,那还Push不了。
传送门🚪远程依赖FlutterModule编译产物(多方案)