我也是刚开始使用flutter,本来第一篇想写一写flutter入门。但是在开发过程中需要把flutter 的module打包集成到iOS原生项目,发现了一些坑,所以写一篇文章mark一下,也帮助大家少走一些弯路。
一般大家用到的flutter 集成到iOS项目的方式有两种,cocoapods集成和直接打成Framework包。
cocoapods 优点是Flutter module有代码改动不需要重新打包,但是要Flutter module的开发者需要安装Flutter sdk和cocoapods. Framework包则相反,开发者不需要安装flutter sdk和cocoapods 但是每次flutter有更改都需要重新打包。
1. 生成framework
这里主要说一下直接打framework的方法。在flutter目录下执行下面指令,会在该目录下生成frameworks文件夹
flutter build ios-framework --output=framework/
如果报下面的错误,因为Dart 2.12之后引入了空安全,所以引用之前版本的包就会报该错误。
For solutions, see https://dart.dev/go/unsound-null-safety
Exception
需要使用下面命令打包
flutter build ios-framework --no-sound-null-safety --output=framework/
打包完成后,会在framework目录下看到相应的framework 三个文件夹,如下里面都包含五个framework文件,可以根据需求集成debug, release, profile。这里说一下profile,profile包是用来做性能调试用的。
以debug为例,如下是相应的五个xcframework包,将其引入到需要集成的Xcode项目中
2. 集成进iOS原生项目
在Xcode项目中target > general > Frameworks, Libraries, and Embedded Content, 点击加号添加这5个framework.也可以直接将五个framework拖动到该区域。
集成后效果如下
添加path, 在target > Build Settings > Search Paths > Framework Search Paths 下添加frameworks对应的路径
\
3.加载Flutter module内容
在需要加载flutter 的地方引入flutter
#import <Flutter/Flutter.h>
实现如下代码,加载flutter的controller的方法有很多种,有兴趣的可以找相关的文章研究,这里只列举一种示例
FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"router"];
[engine run];
FlutterViewController *controller = [[FlutterViewController alloc] initWithEngine:engine nibName:nil bundle:nil];
// modal 全屏
controller.modalPresentationStyle = UIModalPresentationFullScreen;
[self presentViewController:controller animated:false completion:nil];
4. 避坑指南
这是运行模拟器应该能成功拉起flutter ,那么坑来了,我们换成真机试试。这时Xcode会提示无法安装,效果如下,
点击details,会显示下图信息,提示签名不支持。
从网上找了很多资料,基本上主要的解决方法就是将general里面的Embed 改为Do Not Embed
改完之后可以安装了,但是运行起来报了新的错误,针对该错误,好多文章给出的解决方法时将Do Not Embed改为Embed & sign,但是之前的Embed & sign,应用根本安装不上。
最后经过多次尝试,发现只需要更改其中的一个framework的embed方式,如下图,就可以正常运行了,关于embed的选取,涉及到了iOS 静态库和动态库的知识,下面会进行一下扩展,知其然还要知其所以然。
5. iOS 静态库 & 动态库扩展
这里扩展一下XCode framework的静态库和动态库。
静态库:链接时完整地拷贝至可执行文件中,被多次使用就有多份冗余拷贝,存在形式:.a和.framework
动态库:链接时不复制,程序运行时由系统动态加载到内存,供程序调用,系统只加载一次,多个程序共用,节省内存。存在形式:.dylib和.framework
所以framework中既可能是静态库,又可能是动态库。Embed的选取就要根据静态库库还是动态库来决定,通过下面命令查看是动态库还是静态库,运行framework下的同名文件,路径不重要,找到目录下的同名文件就行。
file FlutterPluginRegistrant.xcframework/ios-arm64_armv7/FlutterPluginRegistrant.framework/FlutterPluginRegistrant
会看到下面的结果,表示这个一个静态库,需要使用Do not Embed方式
[arm64:current ar archive random library] [arm_v7:current ar archive random library]
选取一个动态库查看
file App.xcframework/ios-arm64_armv7/App.framework/App
会看到下面的结果,说明这是一个动态库,需要用Embed方式
Mach-O 64-bit dynamically linked shared library arm64
综上,
current ar archive
:说明是静态库,选择Do not embed
Mach-0 dynamically
:说明是动态库,选择Embed
Sign: sign只针对动态库,如果动态库已经有签名,就不需要再签名,可以选择Embed Without Signing。如果没有签名就需要使用Embed & sign。可以用如下名利查看framework是否已签名
codesign -dv App.xcframework.xcframwork
如果返回:
code object is not signed at all
或者adhoc
:选择Embed & sign
- 其它:表示已经正确签名,选择
Embed Without Signing
静态库参考了如下文章: