19年初,准备尝试 Flutter 实现部分业务页面,一个因为 Native 主工程已经较为庞大,一个因为目前只有少部分人投入 Flutter 的尝试,因此不可能重建一个 Flutter 工程来实现整个主工程,而且因为少部分人尝试,所以也不可能每个人都安装 Flutter 环境,所以,在 Native 工程中引入 Flutter 过程中,最好能像引入 其它第三方组件一样通过 pod 引入 Flutter,而其他非 Flutter 的业务开发人员无需安装 Flutter 环境来实现无痕的嵌入使用
接下来我们就关于 Native 工程中引入 Flutter 的一些做法进行简单说明
-
简单的实现
- 新建了一个
flutter_app
的Flutter 工程
,用来实现相关的 flutter 业务,它的目录结构如下
由 Flutter 目录分别包含两个平台的 Native 工程的目录组成,我们通过flutter build ios --release
后可以查看到 ios 的 Flutter 目录下包含了一些构建运行后的产物:
App.framework
- dart代码生成
Flutter.framework
- 引擎
(这块资源在新的Fluterr版本中包含在App.framework中)flutter_assets
- 相关资源
而这也正是 Navtive 工程引入 Flutter 需要的依赖,所以很容易想到收集这些中间产物作为组件内容,并通过 pod 引入即可
⚠️⚠️⚠️以上的产物均为 flutter 平台相关 dart 代码资源,若是引入 plugin,那理应还有 plugin 对应 Navtive 平台的代码,通过图上目录,.flutter-plugins
即项目引用的 plugins 及对应本地路径,也就是说这一步我们目前可知需要的内容如下
.flutter-plugins - 引用的 plugins 及对应本地路径
App.framework - dart代码生成
Flutter.framework - 引擎
flutter_assets - 相关资源
Source - Flutter 工程的原生代码,一些 channel 代码,混合栈的构建代码
- 新建一个
product_flutter
的中间产物模块,Flutter 编译过后的产物
、plugin Navtive平台代码
,Flutter 工程的原生代码 - Source
进行收集放置 product_flutter 这个模块中,然后上传至 git 仓库,目录如下
.podspec
需要⚠️的一个就是 .flutter-plugins
,pluginspodhelper.rb
,plugins
在pod下载后不要删除,否则工程项目分析及pod plugin会缺少文件,所以.podspec
的内容如下:
# MARK: converted automatically by spec.py. @liya
Pod::Spec.new do |s|
s.name = 'product_flutter'
s.version = '1.0'
s.description = 'product_flutter,喵喵'
s.license = 'MIT'
s.summary = 'product_flutter'
s.homepage = 'https://xxx'
s.authors = { 'Liya' => 'xxx@qq.com' }
s.source = { :git => '.../product_flutter', :branch => 'master' }
s.ios.deployment_target = '8.0'
s.vendored_frameworks = 'Flutter.framework', 'App.framework'
s.resources = 'flutter_assets'
s.source_files = 'Source/**/*.{h,m,c}'
s.preserve_paths = 'pluginspodhelper.rb', '.flutter-plugins', 'Plugins/**/*.{*}'
end
收集脚本较为简单,这里就发下 plugins
的收集部分
flutter_res_plugins=$PWD/.flutter-plugins
......
while read -r line
do
if [[ ! "$line" =~ ^// && ! "$line" =~ ^# ]]; then
array=(${line//=/ })
iosClasses=${array[1]}ios/Classes
plugin=$PWD/Plugins/${array[0]}
if [ -d $plugin ]; then
rm -rf $plugin
fi
if [ -d $iosClasses ]; then
mkdir -p $plugin
cp -rf $iosClasses $plugin
// 这里是为了解决 plugins 相关的依赖传递,所以把.podspec也进行包含
podspec=${array[1]}ios/${array[0]}.podspec
if [ -d $iosClasses ]; then
cp -f $podspec $plugin
podspec=$plugin/${array[0]}.podspec
echo "删除 s.dependency 'Flutter' in $podspec"
sed -i '' -e "s/s.dependency 'Flutter'//g" $podspec
fi
echo "plugin $plugin"
echo "iosClasses $iosClasses"
fi
fi
done < $flutter_res_plugins
- 在前两步完成后,考虑到参与 Flutter 项目的人员可能 Flutter 环境不一致等情况,还有就是每次都需要手动 build 并更新上传至 product_flutter 仓库,所以借助 ci 完成部分手动工作,同时 ci Flutter 环境单一,可以确保打出来的中间产物环境一致
在 ci 创建 test_flutter 的任务,设定脚本每天在凌晨进行一次打包后自动上传中间产物至 product_flutter(也可手动去 ci 点击进行打包),整个脚本目前很简单,主要也就是
#...
export PATH=/usr/local/bin:$PATH
export PATH=$PATH:$HOME/XXX/flutter/bin
echo "flutter begin"
flutter packages get
echo "pod install or update"
cd ios
if [ ! -d "$PWD/Pods"]; then
pod install
else
pod update
fi
cd -
echo "flutter release -- "
flutter clean
flutter build ios --release
# 接下来其它操作也仅仅就是将产物拷贝打tag并上传
- 而在需要使用 Flutter 的工程中,调用较为简单,但是同时要注意到
plugin 的 pod 使用
# pod product_flutter,这里进行本地实验,放git上一样
pod 'product_flutter',:git => '../product_flutter'
# 产物收集时同时也收集了.flutter-plugins
# 使用时依然分析一次 .flutter-plugins 并依次对 plugin 进行 pod 引用
flutter_pod_fold ||= File.join(__dir__,"Pods","product_flutter")
flutter_plugin_rb ||= File.join(flutter_pod_fold,"pluginspodhelper.rb")
if File.exists? flutter_plugin_rb
load flutter_plugin_rb
pod_KV_file(flutter_pod_fold)
end
其中 pluginspodhelper.rb
脚本主要内容如下
....
def pod_KV_file(pod_file)
# If this wasn't specified, assume it's two levels up from the directory of this script.
print "进行 pod plugin "
flutter_pod_path ||= File.join(pod_file)
plugin_pods_file = parse_KV_file(File.join(flutter_pod_path, '.flutter-plugins'))
plugin_pods_file.map { |r|
print "plugin_pods_file = ",r
pod r[:name], :path => File.join(flutter_pod_path, 'Plugins', r[:name])
}
end
-
题外话
- 目前是通过
ci 实现定时任务
,打包后自动上传中间产物,也可以通过git hook
判断flutter_app
提交的新代码,然后自动触发ci
进行flutter_app
打包
采取哪种方式或者打包频次根据自身需要即可 - flutter 打包在不同模式下的中间产物具有差异
a.debug
模式下有JIT支持
(所以支持热重载 HotReload),App.framework
只有几个简单的 API,其 Dart 代码生成标记化的源代码,运行时编译,解释执行,存在于flutter_assets
的kernel_blob.bin
文件里
b.release
模式下是AOT支持
,App.framework
是所有的 dart 代码(业务代码,第三方 package 代码,及所依赖的 flutter 框架代码等等)编译所成,Flutter.framework
则是Flutter 架构
中的engine
部分,flutter_assets
是图片,字体等相关资源
-
莫名其妙
也不懂为啥突然想在大年初一写这个,也没完全规划好,写的也很简单,不过本身远程依赖这一步并不复杂~~权当作新年开关好了~~ON 一下~~
=======================后续的一点补充===============================
-
Native 主工程动态引入Debug及Release环境的中间产物包
因为如题外话所说,flutter
打包的产物在不同模式下是有差异的,所以导致在开发模式中需要用的Debug,但是打包上架需要Release,而作为Flutter开发人员,有时有希望能在本地主工程引用中看效果,因此可能需要手动切换等,耗时耗力,这真是万万不能忍呀~~,所以对这块的脚本及流程进行了一些稍稍优化
1 . 收集产物时,进行两模式的分开收集,一是 Debug 模式,一是 Release 模式
flutter build ios --no-codesign --debug
//跟以上一样收集对应的真机的debug产物
flutter build ios --simulator --debug
//同上并合并
lipo -create $res_dir/App.framework/App $target_debug_dir/App.framework/App -output $target_debug_dir/App.framework/App
....
Release 模式的
flutter build ios --no-codesign --release
//收集Release产物,并注意要将注册文件一起收集到Source中
cp {$source_runner_res_dir/GeneratedPluginRegistrant.h,$source_runner_res_dir/GeneratedPluginRegistrant.m} $target_source_dir
- 收集产物后,就是pod的相关操作了
1). 将 Flutter Debug 及 Release 产物分别对应一个 podspec 文件,而个人工程中相关的原生代码对应一个 podspec 文件,引用如下,即可解决不同模式下引用,方便了手动操作
( PS 这个如果有更好的 framework search 路径设置及 flutter_assets 资源路径设置方法,请留言,之前尝试过直接在 podfile 中设置,依然有各种问题 )
pod 'LYFlutter', :git => "...", :branch => branch
pod 'FlutterFrame_Debug', :git => "...", :branch => branch, :configurations => 'Debug'
pod 'FlutterFrame_Release', :git => "...", :branch => branch, :configurations => 'Release'
当然,在实际使用中均放置在 FlutterPodHeleper.rb 脚本中,并在其中下载 .flutter-plugins文件,并解析并pod相关文件
# flutter远程依赖 - release的产物
# $flutter_pod_path : 工程 pods 的路径
# $flutter_branch : LYFlutter的分支,未设置默认 "master"
$flutter_pod_path = __dir__+"/Pods"
eval(File.read(File.join(__dir__, 'FlutterPodHelper.rb')), binding)
# flutter本地依赖
# $flutter_application_path : flutter工程的路径
$flutter_application_path = "/Users/.../Liya_flutter"
eval(File.read(File.join(__dir__, 'FlutterPodHelper.rb')), binding)
post_install hook
post_install do |installer|
#调用配置 - bitcode设置
update_flutter_configs(installer, $flutter_application_path)
end
- FlutterPodHeleper.rb 脚本实现主要如下,其实就是之前的 pluginspodhelper .rb 脚本,改后承接较多的内容
# 远程的plugins文件解析
def pod_remote_flugins_file(plugin_helper_local_path, pod_file)
# If this wasn't specified, assume it's two levels up from the directory of this script.
print "解析并写入pod plugin \n"
plugin_pods_file = parse_KV_file(File.join(plugin_helper_local_path))
plugin_pods_file.map { |r|
print "plugin_pods_file = ",r[:name]," \n"
pod r[:name], :path => File.join(pod_file, 'LYFlutter/Plugins', r[:name])
}
end
# 远程的plugins文件下载并解析
def down_remote_plugins_file(branch, pod_file)
lyflutter_url = "https://.../LYFlutter"
lyflutter_git_url = lyflutter_url+".git"
plugin_helper_url = lyflutter_url+"/raw/"+branch+"/PluginTool/.flutter-plugins"
plugin_helper_local_path = './.flutter-plugins'
print "下载 plugins 解析文件 \n"
download = open(plugin_helper_url)
IO.copy_stream(download, plugin_helper_local_path)
pod 'LYFlutter', :git => lyflutter_url, :branch => branch
pod 'FlutterFrame_Debug', :git => lyflutter_url, :branch => branch, :configurations => 'Debug'
pod 'FlutterFrame_Release', :git => lyflutter_url, :branch => branch, :configurations => 'Release'
pod_remote_flugins_file(plugin_helper_local_path, pod_file)
print "删除下载 plugins 相关文件 \n"
File.delete(plugin_helper_local_path)
end
本地的引用
# 本地的引用
def local_remote_plugins_file(flutter_application_path)
native_application_path = File.join(flutter_application_path, 'ios')
framework_dir = File.join(native_application_path, 'Flutter')
pod 'Flutter', :path => native_application_path
pod 'LYFlutter', :path => native_application_path
symlinks_dir = File.join(native_application_path, '.symlinks')
FileUtils.mkdir_p(symlinks_dir)
plugin_pods = parse_KV_file(File.join(flutter_application_path, '.flutter-plugins'))
plugin_pods.map { |r|
symlink = File.join(symlinks_dir, 'plugins', r[:name])
FileUtils.rm_f(symlink)
File.symlink(r[:path], symlink)
pod r[:name], :path => File.join(symlink, 'ios')
}
end
如上基本就是引用脚本的主要代码了,远程依赖及产物收集脚本及目录结构详情点🔗
这文章组织上自己没做好,就将就吧~~😂