欢迎重视微信大众号:FSA全栈举动
一、概述
关于 Shorebird
的初始化内容能够在上一篇《Flutter – 混编项目集成Shorebird热更新(安卓篇)》中检查,这里就不再赘述了。
Shorebird
官方文档上对于 iOS
混编方案集成热更新的介绍不算详细,只能说点明晰要点,指明晰方向。
本文将依据实际的项目运用状况做出集成调整,并补充阐明正确的补丁验证方案。
二、踩坑
Shorebird
文档里指出需求咱们运用类似 Flutter
官方文档里 Option B - Embed frameworks in Xcode
的办法去集成 Flutter
模块。
关于
Flutter
官方文档指出的各种集成办法能够检查: docs.flutter.dev/add-to-app/…
详细的步骤便是:
- 先注释掉本来的
Option A
集成办法的相关装备代码 - 履行
Shorebird Release
去构建对应的一切xcframework
文件。(xcframework
文件包括了Flutter.xcframework
和App.xcframework
,以及插件依靠的原生第三方库对应的xcframework
) - 将一切构建完结的
xcframework
拖到Build Phases
中的Embed Frameworks
内。 - 将
xcframework
的地点目录途径装备到Framework Search Paths
。 - 装备
xcframework
的Embed
形式,静态库有必要选Do Not Embed
,动态库有必要选Embed & Sign
。
因为 Option A - Embed with CocoaPods and the Flutter SDK
的办法只需求简略的装备 Podfile
就能够集成 Flutter
模块,所以信任咱们在一般状况下都是会挑选 Option A
的办法。很显着,要改成 Option B
需求咱们大改特改。
改成 Option B
这种办法有以下几点问题:
1、vendored_frameworks
缺失
假设你依靠的 Flutter
插件依靠了原生第三方的二进制包,如 realm
,在它的 podspec
文件是这样声明的 s.vendored_frameworks = 'realm_dart.xcframework'
,那你会发现在终究构建完结的 xcframework
的目录里会短少这些 vendored_frameworks
。
相关的 issue
: github.com/flutter/flu…。
因为 Option B
是二进制依靠,所以在编译的时候并不会报任何过错,等你 App
运转起来进入一些相关场景,运用到了对应的第三方功用时就会直接来个找不到符号的过错,如:
Failed to lookup symbol 'native_method_signature': dlsym(0xa47e7c10, native_method_signature): symbol not found
接着便是闪退,可想而知这得多吓人!
2、重复编译
vendored_frameworks
缺失的问题我通过脚本处理了,但是还有另一个问题,这些 xcframework
中也有可能出现涵盖你本来的原生工程里依靠的第三方包,比方,Flutter
的插件用到了 FMDB
,生成的 xcframework
中就会包括 FMDB.xcframework
,而你的原生工程本来就有依靠 FMDB
,这个时候编译,Xcode
就会告诉你重复了,编译不通过,报错内容如下:
Showing Recent Messages
Multiple commands produce '/Users/lxf/Library/Developer/Xcode/DerivedData/xxx.app/Frameworks/FMDB.framework'
假设是你,你挑选留下哪个呢?
- 假设你挑选了
Flutter
帮你生成的FMDB.xcframework
,你就得去处理其它原生第三方依靠的pod 'FMDB'
,假设此刻原生工程里的一些第三方库或私有库也依靠FMDB
,那你要处理这些库可就太麻烦了。 - 假设你挑选运用
pod 'FMDB'
的办法,那你只需求去判别原生工程里是否有对应的依靠,有的话就不再声明依靠,这种还好。
3、静态库与动态库
生成的 xcframework
中,有些是静态库,有些是动态库
如图所示,静态库有必要选 Do Not Embed
,动态库有必要选 Embed & Sign
。
假设你全选了 Embed & Sign
,那么你就无法启动 App
了,如下图所示
该问题的相关 issue
: github.com/flutter/flu…
所以为了避免这种状况,咱们就有必要得选对 Embed
选项,能够运用 file
指令去判别 xcframework
是静态库仍是动态库
file FlutterPluginRegistrant.xcframework/ios-arm64/FlutterPluginRegistrant.framework/FlutterPluginRegistrant
FlutterPluginRegistrant.xcframework/ios-arm64/FlutterPluginRegistrant.framework/FlutterPluginRegistrant:
current ar archive random library // 静态库
file url_launcher_ios.xcframework/ios-arm64/url_launcher_ios.framework/url_launcher_ios
url_launcher_ios.xcframework/ios-arm64/url_launcher_ios.framework/url_launcher_ios:
Mach-O 64-bit dynamically linked shared library arm64 // 动态库
这部分判别逻辑只能交给脚本处理了,因为当数量起来后你就会体验到什么叫溃散,别问我是怎样知道的
4、直接溃散
后面我直接用脚本判别 Flutter
插件依靠了哪些原生第三方,将它们统一在原生工程内声明依靠,在一些状况下这也是很风险的,如 connectivity_plus
这个 Flutter
插件依靠了 ReachabilitySwift
,你有必要得运用 Reachability.xcframework
二进制嵌入的办法,不然运转就崩~
dyld[31764]: Symbol not found: _$s12ReachabilityAAC10ConnectionO4wifiyA2DmFWC
Referenced from: <8142F86E-4C9C-3513-AD29-D3522FC6677F> /Users/lxf/Library/Developer/Xcode/DerivedData/xxx/connectivity_plus.framework/connectivity_plus
Expected in: <DA318000-9A97-35AD-87EA-7C5B635DE010> /Users/lxf/Library/Developer/xxx.app/Frameworks/Reachability.framework/Reachability
三、分析
后来仔细想想,Shorebird
的热更新是针对 Dart
代码,跟原生无关,能不能按本来的 Cocoapods
办法去集成 Flutter.xcframework
,App.xcframework
以及插件依靠的原生第三方库呢?
答案是能够的,来看看 install_all_flutter_pods
办法
def install_all_flutter_pods(flutter_application_path = nil)
...
flutter_application_path ||= File.join('..', '..')
# 生成 .ios/Flutter/Flutter.podspec
install_flutter_engine_pod(flutter_application_path)
# 集成 插件依靠的原生库 Pods
install_flutter_plugin_pods(flutter_application_path)
# 编译并集成 Flutter.xcframework 和 App.xcframework
install_flutter_application_pod(flutter_application_path)
end
1、install_flutter_engine_pod
install_flutter_engine_pod
生成的 Flutter.podspec
是假的podspec
,里边没啥实质内容,仅代表 Flutter.xcframework
,为什么要这么做呢?因为一些 Flutter
插件声明需求依靠 Flutter
,如:
Pod::Spec.new do |s|
s.name = 'sqflite'
...
s.dependency 'Flutter'
s.dependency 'FMDB', '>= 2.7.5'
...
end
假设没有这个 Flutter.podspec
,那么履行 pod install
就会从 CocoaPods trunk
下载 Flutter
了。
2、install_flutter_application_pod
install_flutter_application_pod
会去编译 Flutter.xcframework
和 App.xcframework
,并将它们并集到咱们的原生工程内。不过这两玩意咱们用 Shorebird Release
去生成了,所以这个办法咱们用不上。
咱们能够结合上述的 Flutter.podspec
的作用,修正它内部的依靠声明,然后完成通过 Cocoapods
的办法来集成 Flutter.xcframework
和 App.xcframework
。
Pod::Spec.new do |s|
s.name = 'Flutter'
s.version = '1.0.0'
s.summary = 'A UI toolkit for beautiful and fast apps.'
s.homepage = 'https://flutter.dev'
s.license = { :type => 'BSD' }
s.author = { 'Flutter Dev Team' => 'flutter-dev@googlegroups.com' }
s.source = { :git => 'https://github.com/flutter/engine', :tag => s.version.to_s }
s.ios.deployment_target = '11.0'
# Framework linking is handled by Flutter tooling, not CocoaPods.
# Add a placeholder to satisfy `s.dependency 'Flutter'` plugin podspecs.
#
# 以上到这句都是本来的,将这句注释掉
+ # s.vendored_frameworks = 'path/to/nothing'
# 新增下面这句,声明依靠当前目录下的 Flutter.xcframework 和 App.xcframework
+ s.vendored_frameworks = 'Flutter.xcframework', 'App.xcframework'
end
生成的一切 xcframework
地点途径为: xxx/flutter_module/build/ios/framework/Release
, 咱们自己创立的 Flutter.podspec
中的依靠是相对途径,所以该 podspec
也是跟 xcframework
放到一同,当然也能够依据你自己的习气进行调整。
3、install_flutter_plugin_pods
install_flutter_plugin_pods
会将 Flutter
插件依靠的原生库集成到咱们的原生工程,这正是咱们需求的。
不过假设你直接将 Podfile
中的 install_flutter_application_pod
给替换成 install_flutter_plugin_pods
,履行 pod install
时是会报如下过错的:
pod install
[!] Invalid `Podfile` file: undefined method `flutter_relative_path_from_podfile' for #<Pod::Podfile:0x000000010e74c520 @defined_in_file=#<Pathname:/Users/lxf/xxx/Podfile>, @internal_hash={}, @root_target_definitions=[#<Pod::Podfile::TargetDefinition label=Pods>], @current_target_definition=#<Pod::Podfile::TargetDefinition label=Pods>>
relative = flutter_relative_path_from_podfile(export_script_directory)
也便是找不到 flutter_relative_path_from_podfile
办法,因为该办法在并不在你的 Flutter
模块的 podhelper.rb
中,而是在 packages/flutter_tools/bin/podhelper.rb
。
至于为什么本来的 install_all_flutter_pods
办法不会报错,是因为在该办法内先引用了 flutter_tools/bin/podhelper.rb
。
要害代码如下:
def install_all_flutter_pods(flutter_application_path = nil)
...
# 便是这句
require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
flutter_application_path ||= File.join('..', '..')
install_flutter_engine_pod(flutter_application_path)
install_flutter_plugin_pods(flutter_application_path)
install_flutter_application_pod(flutter_application_path)
end
所以咱们能够依样画葫芦,在 install_flutter_plugin_pods
办法中参加 require
这一行代码,以处理上述过错。
def install_flutter_plugin_pods(flutter_application_path)
+ require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
flutter_application_path ||= File.join('..', '..')
...
end
老是这么改也不是个办法,所以我提了个 PR
: github.com/flutter/flu…
该
PR
现已合并,应该会在3.16.9
及之后的版本中生效。
通过验证,该方案是可行的,下面咱们来看看如何调整原生工程和 Shorebird
在 iOS
混编下如何运用吧。
四、原生工程调整
在 Podfile
文件中,将 Flutter
壳工程的源码依靠办法调整为二进制依靠
- install_all_flutter_pods(flutter_application_path)
+ # 源码集成
+ # install_all_flutter_pods(flutter_application_path)
+ # 二进制集成
+ pod 'Flutter', path: 'xxx/flutter_modules/build/ios/framework/Release'
+ install_flutter_plugin_pods(flutter_application_path)
- 声明
Flutter
依靠,用于集成Flutter.xcframework
和App.xcframework
。 -
Option A
办法所需求的代码统统保留,只需求将install_all_flutter_pods
替换为install_flutter_plugin_pods
,用于集成Flutter
插件所依靠的原生第三方库
五、创立 Shorebird Release
打发布包的时候操作,在 Flutter
工程目录下履行
cd xx/xx/flutter_modules
# 7.0.0+2: 版本号+build版本号
shorebird release ios-framework-alpha --release-version 7.0.0+2
该指令内部会去履行
flutter build ios-framework --no-debug --no-profile ...
,并且运用的是Shorebird
魔改的Flutter
引擎!
版本号能够在如下图所示进行检查
ShoreBird
的内部逻辑会去以这个版本号组合,向服务器恳求判别是否存在相应版本的相关补丁!
履行完结后,在 Shorebird
操控台上能够看到相应的项
在指令履行前,请确保不存在 7.0.0+2
的 Release
,假设有的话,请先删去
六、创立 Shorebird Patch
紧急修复线上包的bug时操作,在 Flutter
工程目录下履行
shorebird patch ios-framework-alpha --release-version 7.0.0+2
注:版本号与上述的 release
指令中运用的要保持一致!
履行完结后,在 Shorebird
操控台上点击对应的 Release
项,进去后能够看到相应的补丁
看看这个补丁巨细,咱们再来看看安卓的补丁巨细
一样的修正,安卓的补丁巨细不到
2 MB
,iOS
的补丁巨细高达54.83 MB
七、热更新验证
官方文档上就仅仅说重启
App
检查补丁是否生效,并没有阐明失利了该假设排查问题~
1、在履行完 shorebird release
指令并完结上述原生工程的调整后,将原生工程的编译形式调整为 Release
进行编译。
此刻会依靠的 flutter_modules/build/ios/framework/Release
下的 xcframework
,备份为 Release_release
2、封闭 App
,打 patch
,留意,此刻 flutter_modules/build/ios/framework/Release
下的内容会被清空偏从头创立。
3、打 patch
后,将 Release_release
改回 Release
用 Xcode
从头运转 App
,一切正常的话即可看到变化。
无论成功仍是失利,Xcode
的操控台都会有相应的输出
成功
2024-01-03 18:37:55.838328+0800 xxx[623:70498] [VERBOSE0:shorebird.cc(151)] Shorebird updater: no active patch.
2024-01-03 18:37:55.838424+0800 xxx[623:70498] [VERBOSE0:shorebird.cc(155)] Starting Shorebird update
[00:00:00.002] (1701cb000) INFO Sending patch check request: PatchCheckRequest { app_id: "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", channel: "stable", release_version: "7.0.0+2", patch_number: None, platform: "ios", arch: "aarch64" }
[00:00:30.871] (1701cb000) INFO Patch 1 successfully installed.
[00:00:30.871] (1701cb000) INFO Update result: Update installed
失利
能够搜索要害字
PatchCheckRequest
定位
2024-01-03 18:37:55.838328+0800 xxx[623:70498] [VERBOSE0:shorebird.cc(151)] Shorebird updater: no active patch.
2024-01-03 18:37:55.838424+0800 xxx[623:70498] [VERBOSE0:shorebird.cc(155)] Starting Shorebird update
[00:00:00.002] (1701cb000) INFO Sending patch check request: PatchCheckRequest { app_id: "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", channel: "stable", release_version: "7.0.0+2", patch_number: None, platform: "ios", arch: "aarch64" }
[00:00:30.871] (1701cb000) ERROR Update failed: error decoding response body: operation timed out
Caused by:
operation timed out
[00:00:30.871] (1701cb000) INFO Update thread finished with status: Update had error
该失利是因为国行机特有的网络权限导致的,敞开 Shorebird
的主动检查更新的话,会在网络权限被赋予前去恳求,结果便是失利,所以需求封闭主动检查更新,运用 shorebird_code_push 去推迟检查。
八、脚本
因为咱们日常研发仍是运用的是源码依靠的办法,只会在打终究测验包时才需求去做上述的调整操作,所以这里用我比较熟悉的 Python
去制作了简易的脚本,并结合 Jenkins
来辅助完结这种万年不变的无聊步骤
脚本已上传至 Github
: github.com/LinXunFeng/…
看官可自取修正~
switch_flutter_integrate.py
切换
Flutter
项目的集成办法
# 二进制依靠
python switch_flutter_integrate.py -p '原生工程途径' -m 'binary' -f 'ios'
# 源码依靠
python switch_flutter_integrate.py -p '原生工程途径' -m 'source' -f 'ios'
shorebird.py
主动获取版本号,并履行
Shorebird
相关指令
# release
python shorebird.py -p '原生工程途径' -s 'Flutter工程途径' -m release -f ios
# patch
python shorebird.py -p '原生工程途径' -s 'Flutter工程途径' -m patch -f ios
需求留意的是,xcodeproj
和 target
的姓名被我固定写成 OCProject
,如下代码中高亮的那两行,咱们请先将其修正为自己的工程名再运用 shorebird.py
。
def handle_ios():
"""
处理iOS项目
"""
# 1. 读取主版本号
# 请将 OCProject 修正为你们自己的工程名
+ xcodeproj_path = os.path.join(project_path, 'OCProject.xcodeproj')
version = ReleaseVersionTool.fetch_project_version(
xcodeproj_path=xcodeproj_path,
+ target_name='OCProject',
)
因为我比较懒,就不改成通用的了
九、最终
尽管 iOS
的热更新能用,但也仅仅仅仅能用,运用于很简略的运用程序,运转起来没有太显着的卡顿感知,但是略微大点就能够感知到了,卡到怀疑人生那种,相比安卓端的没有任何性能损耗,iOS端的还需求再等等,究竟现在 iOS
仍是 Alpha
版本,信任不久将来 Shorebird
团队会处理该问题。
详细关于安卓和 iOS
两头之间的完成差异能够在这个 issue
中检查 github.com/shorebirdte…
本篇到此结束,感谢咱们的支撑,咱们下次再见!
假设文章对您有所协助, 请不惜点击重视一下我的微信大众号:FSA全栈举动, 这将是对我最大的鼓励. 大众号不只有
iOS
技能,还有Android
,Flutter
,Python
等文章, 可能有你想要了解的技能知识点哦~