Skip to content
This repository has been archived by the owner on Nov 27, 2023. It is now read-only.

Latest commit

 

History

History
276 lines (195 loc) · 17.7 KB

add_flutter_to_ios.md

File metadata and controls

276 lines (195 loc) · 17.7 KB

在iOS项目中依赖Flutter组件代码

  目录

不管用何种方式在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

cdsome/path/flutter_module/,用于执行Flutter指令。

建好flutter_module后,随便加点flutter代码和第三方组件,就可以测试添加到iOS项目了。下面我们来尝试几种导入/依赖方案,前3种是官方推荐的,Flutter也有相关的开发文档 Adding Flutter to iOS

1.基于CocoaPods和podhelper.rb脚本本地依赖FlutterModule

这种接入方式是最常见的一种,方便入手,代码也方便拆分,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)

2.编译FlutterModule,手动添加.xcframwork到iOS项目中

首先需要将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/Release3种不同模式的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 PhasesLink 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 EmbedFlutter文档说明中也指明了这个操作,都选择Embed & Sign即可,设置正确后就能正常运行项目了。

这种导入framework的方式,增加了编译Flutter、设置Target配置流程,如果需要切换Debug/Release环境,还需要重新添加framework,并重新设置FRAMEWORK_SEARCH_PATHSEmbed & Sign,在调试期间会增加不少的手动操作,当然为了方便调试,在flutter_module/.ios/下面的Runner项目中也可以依赖iOS的业务代码,也可以快速调试,只是Flutter clean后又要重新依赖,相对来说还是有点繁琐的;另外由于把编译产物直接导入到了iOS项目目录中,而Flutter.xcframework文件很大,会直接增加git的文件大小,影响git push和pull,每次编译也会影响到其他人员分支的同步。但这种导入framework的方式也有个非常大的优点,编译运行iOS项目耗时短,因为已经是编译过的xcframework文件,不用每次附加编译Flutter代码,相比之下能节省很多编译时间;另外其他的开发人员也不用安装Flutter开发环境,直接跑iOS项目就行。

  • 模拟器上运行不能正常展示Flutter页面,是空白的,待排查原因

3.编译FlutterModule,远程依赖Flutter.xcframework,本地依赖其余.xcframwork

前面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.xcframeworkFlutterPluginRegistrant.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.xcframeworkFlutterPluginRegistrant.xcframework第三方库 flutter_boost.xcframework导入到项目中。不过这里我不使用Add Files to 'a project'来添加文件了,而是把这3个文件拖到Frameworks, Libraries, and Embedded Content里面,设置Embed & Sign,然后在Build SettingsRunpath Search Paths添加"$(SRCROOT)/FlutterFrameworks/Release",就可以正常运行项目了。

相比方案2Flutter.xcframework采用了CocoaPods依赖导入,但是其它的.xcframework还是要手动导入。所以它的优缺点和方案2是基本一致的。另外在编译过程中可以看到生成了Flutter.xcframework,但是并没有发现上传文件,所以Flutter.xcframework是远程的静态资源,如果有自定义引擎需求,就得在方案2的基础上改了。

  • 模拟器上运行不能正常展示Flutter页面,是空白的,待排查原因

4.远程依赖FlutterModule编译产物(简单版)

方案3 使用CocoaPods远程依赖Flutter.xcframework中提到,Flutter.xcframework是远程依赖的,那同样也可以远程依赖App.xcframeworkFlutterPluginRegistrant.xcframework其它第三方库 比如 flutter_boost.xcframework,网上也有现成的实现方案和脚本(几乎都是旧版本的,需要自己改改)。

这个方案基于方案2方案3来实现,开头都是先编译,然后收集编译产物,不过这里跟前面的方案不一样,要把收集到的编译产物推送到单独的私有Git仓库,并打上标签。

之所以说可以基于方案2方案3来实现,是因为如果不需要自定义Flutter Engine,使用各个版本默认的Flutter.fromework,那就选择方案3的指令,而且方案3指令编译出来的framework都已经设置了EmbedFlutter.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.xcframeworkFlutterPluginRegistrant.xcframework其它第三方库 比如 flutter_boost.xcframework放到了同一个git目录,并且合并成了一个库,即FlutterModuleSDK。所以需要额外创建一个描述文件FlutterModuleSDK.podspecpodspec的主要内容如下,重点则在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不了。

5.远程依赖FlutterModule编译产物(多方案)

传送门🚪远程依赖FlutterModule编译产物(多方案)

6.远程依赖FlutterModule编译产物(升级版)

传送门🚪远程依赖Flutter Module产物 + Git Submodule + Shell脚本 (升级版 )

回到顶部🔝