[Flutter]关于IOS打包的N个坑及出坑指南(我可能用了个假框架.jpg)

8,900 阅读7分钟

为追求项目进度,公司的最新App使用了flutter框架开发,纯flutter,没有和原生混编,于是就这样开启了一条踩坑之路。咱也不知道是不是Google家和Apple家的东西八字不合,还是因为自己水平太菜打开方式不对,总之一路遇到了各种各样的坑,在这里写出来作为记录,如果有同学遇到了相似的问题能从这篇文章收获点思路那就最好不过了。

废话不多说开始正片。

场景一:运行flutter build ios时可能的报错

关键字Dart snapshot generator failed with exit code -xx

flutter项目在构建release版本的包时,会使用AOT模式进行编译,flutter会将dart库和我们写的代码编译、链接成App.framework,作为原生的一个依赖。其中编译这一步由dart-sdk中的gen_snapshot这个可执行文件负责,可以在flutter根目录下的ios/.symlinks/flutter/ios-release/gen_snapshot中看到这个可执行文件。它的内部逻辑代码可以在dart在github的开源代码中sdk/runtime/bin/gen_snapshot.cc中找到,具体的逻辑就不再赘述了,之后打算写一篇详细介绍flutter打包编译过程的文章,先立个flag。

总之当看到Dart snapshot generator failed with exit code -xxx的时候,基本可以确定是dart在编译的阶段出了什么问题,而且这时通常会带有其他的错误信息。下面这两个例子就是在本项目中遇的到坑。

1.Dart snapshot generator failed with exit code -6

完整报错信息

=== BUILD TARGET Runner OF PROJECT Runner WITH CONFIGURATION Release ===
    Building AOT snapshot in release mode (ios-release)...
    Unexpected object (Class with illegal cid, full-aot): 0x10ea8e471 Library:'dart:collection' Class:
    _CompactLinkedHashSet@3220832

    Dart snapshot generator failed with exit code -6
    Building AOT snapshot in release mode (ios-release)...             43.6s
    Built to build/aot/.
    Snapshotting (IOSArch.arm64) exited with non-zero exit code: -6
    cp: build/aot/App.framework: No such file or directory
    error: cannot parse the debug map for 'build/aot/App.framework/App': No such file or directory
    Failed to generate debug symbols (dSYM) file for build/aot/App.framework/App.

当exit code为-6时,说明在编译时有些类型没有找到,关键信息就在Unexpected object (Class with illegal cid, full-aot): 0x10ea8e471 Library:'dart:collection' Class:_CompactLinkedHashSet@3220832这一句。说明dart没有找到_CompactLinkedHashSet这个类。

dart在编译前会执行一个摇树优化的过程,把不需要的类型删去,以减小包体积。在之前的flutter版本中,dart的摇树优化有bug,有可能无法正确处理一些场景,如果你定义了一个类,没有创建类的实例,却使用了它的类型,摇树优化就会把它删掉,就像这样⬇️:

class XXX {}
void main() { 
    print(const [XXX]);
}

编译时就会报错:找不到XXX类。

但是本项目中的报错是Library:'dart:collection'Class:_CompactLinkedHashSet@3220832,collection是dart的官方库,我们也没有像上面那样在项目中使用它。本项目中错误的原因是我们在项目中写了main_dev.dartmain_pro.dart两个文件作为项目的入口文件,这两个入口的main()函数中调用了main.dartmain()函数。而flutter build命令还是将main.dart文件作为入口,所以在真实的入口文件中定义的东西就被摇树优化掉了。

解决办法: 运行flutter build ios时加上--target=/lib/main_pro.dart,显式指定入口文件

2.Dart snapshot generator failed with exit code 1

同样是运行gen_snapshot时的问题,完整报错:

Building AOT snapshot in release mode (ios-release)...
arch: posix_spawnp: /usr/local/flutter/bin/cache/artifacts/engine/ios-release/gen_snapshot: Bad CPU type in executable

Dart snapshot generator failed with exit code 1

Building App.framework for arm64...
Building AOT snapshot in release mode (ios-release)...             63.0s
Built to build/aot/.
Snapshotting (IOSArch.armv7) exited with non-zero exit code: 1
Snapshotting (IOSArch.arm64) exited with non-zero exit code: 0

Bad CPU type in executable这个错误并不是在执行gen_snapshot的过程中产生的,而是说无法执行gen_snapshot。实际上,在执行gen_snapshot时具体的命令是这样的

/usr/bin/arch -i386 /usr/local/flutter/bin/cache/artifacts/engine/ios-release/gen_snapshot --causal_async_stacks --deterministic --snapshot_kind=app-aot-assembly
--assembly=build/aot/armv7/snapshot_assembly.S --no-sim-use-hardfp
--no-use-integer-division build/aot/app.dill

命令的开头先用arch命令指定执行的架构集,在macos上,i386对应32位,x86_64对应64位,(对应的,在ios中armv7armv7s是32位,arm64arm64e是64位),所以,上面这个命令是用32位的指令架构集执行gen_snapshot脚本,并生成iosarmv7架构的snapshot_assembly.S文件。根据你在Xcode项目中的指定的Valid ArchitectureBuild Valid Architecture Only参数 ,如果两种架构都选了,就会再执行一个的64位版本命令。

可以从倒数第一行看出64位版本的命令是执行成功了的,而32位的却失败了,Bad CPU type in executable没有出现在dart-sdk的源码中,这个错误是macos报出的,因为无法执行gen_snapshot脚本。然而目前只知道在我的电脑上无法用 i386架构执行gen_snapshot,却不清楚这个错误来源于我的电脑还是gen_snapshot本身,因为我的电脑是可以打包32位原生App的,打包flutter App却不行,不过好在现在ios64位架构已经比较普及了,对于手机来说,只有iphone5c及之前的设备是使用32位CPU的。所以目前我采取的策略是不构建armv7armv7s架构,具体操作是在Xcode->Target->Build Setting中将Build Active Architecture Only设为YES,并将Vaild Architecture设为[arm64, arm64e]

这样可以强制flutter只打包64位版本。但是如果你引入了插件的话就还不够,flutter默认用cocoapods管理原生端的插件,在pod中的第三方库也是默认打包两个架构的, 需要podfile中加入配置

post_install do |installer|
    installer.pods_project.targets.each do |target|
        target.build_configurations.each do |config|
            # 只打包64位需要做的配置(下面两行)
            config.build_settings['ARCHS'] = 'arm64'
            config.build_settings['ONLY_ACTIVE_ARCH'] = 'YES'
       end
    end
end

这样就可以了。虽然感觉这样并没有解决根本问题,32位的包还是没有成功编译,但是因为这个项目只在手机上运行,也没有兼容老设备的需求,所以现在看起来没有什么大碍,之后我还是会继续寻找解决方案,也欢迎踩过这个坑的同学留下解决办法~

flutter build ios的坑踩完了,然而有一个问题,flutter build出来的是.app文件,如果要上传到App Store,需要.ipa文件,所以还是要用Xcode->Product->Archive进行打包。于是,新的坑出现了。。。

场景二:运行Xcode->Product->Archive时可能的报错

1.Flutter build must run in release mode

这个错误来源于这行命令/bin/sh "$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh" build,你可以在Xcode->Build Phases-> Run Script中看到这行代码。如果打包时没有指定mode就会报错,需要在Build Settings中加入FLUTTER_BUILD_MODE字段,并根据不同环境设置为debugrelease,一定要对应好,当设置为release时是无法在模拟器上运行的,所以不要在debug环境把这个字段设为release

2.xxx.h file not found

这个错误出现在Complie GeneratedPluginRegistrant.m这个步骤中,解决方法比较简单,运行Xcode->Product->Clean并在flutter/ios文件夹内运行pod install

3.其他

flutter中经常会出现各种打不了包\运行不了模拟器的情况,其中很多都和缓存有关,根据个人的经验,可以进行以下操作(前5步不分先后):

  • 执行flutter clean
  • 运行Xcode->Product->Clean
  • 删除ios/.symlinks文件夹
  • 删除ios/Pods文件夹
  • 删除ios/Podfile.lock
  • 在ios文件夹下执行pod install
  • 重新打包/编译

这一系列操作能解决很多因为缓存导致的问题,如果你的flutter项目突然出了奇奇怪怪的问题不能打包,不妨先试一次,说不定就OK了。🙃