Flutter远程依赖简单实践

  19年初,准备尝试 Flutter 实现部分业务页面,一个因为 Native 主工程已经较为庞大,一个因为目前只有少部分人投入 Flutter 的尝试,因此不可能重建一个 Flutter 工程来实现整个主工程,而且因为少部分人尝试,所以也不可能每个人都安装 Flutter 环境,所以,在 Native 工程中引入 Flutter 过程中,最好能像引入 其它第三方组件一样通过 pod 引入 Flutter,而其他非 Flutter 的业务开发人员无需安装 Flutter 环境来实现无痕的嵌入使用
   接下来我们就关于 Native 工程中引入 Flutter 的一些做法进行简单说明

  • 简单的实现

  1. 新建了一个 flutter_appFlutter 工程,用来实现相关的 flutter 业务,它的目录结构如下

       由 Flutter 目录分别包含两个平台的 Native 工程的目录组成,我们通过 flutter build ios --release 后可以查看到 ios 的 Flutter 目录下包含了一些构建运行后的产物:
    App.framework - dart代码生成
    Flutter.framework - 引擎
    flutter_assets - 相关资源(这块资源在新的Fluterr版本中包含在App.framework中)
      而这也正是 Navtive 工程引入 Flutter 需要的依赖,所以很容易想到收集这些中间产物作为组件内容,并通过 pod 引入即可
      ⚠️⚠️⚠️以上的产物均为 flutter 平台相关 dart 代码资源,若是引入 plugin,那理应还有 plugin 对应 Navtive 平台的代码,通过图上目录,.flutter-plugins 即项目引用的 plugins 及对应本地路径,也就是说这一步我们目前可知需要的内容如下
.flutter-plugins - 引用的 plugins 及对应本地路径
App.framework - dart代码生成
Flutter.framework - 引擎
flutter_assets - 相关资源
Source - Flutter 工程的原生代码,一些 channel 代码,混合栈的构建代码
  1. 新建一个 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
  1. 在前两步完成后,考虑到参与 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并上传
  1. 而在需要使用 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
  • 题外话

  1. 目前是通过 ci 实现定时任务,打包后自动上传中间产物,也可以通过 git hook 判断 flutter_app 提交的新代码,然后自动触发 ci 进行 flutter_app 打包
    采取哪种方式或者打包频次根据自身需要即可
  2. flutter 打包在不同模式下的中间产物具有差异
    a. debug 模式下有JIT支持(所以支持热重载 HotReload),App.framework 只有几个简单的 API,其 Dart 代码生成标记化的源代码,运行时编译,解释执行,存在于 flutter_assetskernel_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

Flutter 是 Debug 模式下的产物,Flutter_Release 是 Release 模式产物,与第一次的区别在图中

pod 执行脚本及 plugin 引用在 PluginTool 文件夹中
  1. 收集产物后,就是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
  1. 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

如上基本就是引用脚本的主要代码了,远程依赖及产物收集脚本及目录结构详情点🔗

这文章组织上自己没做好,就将就吧~~😂

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 151,688评论 1 330
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 64,559评论 1 273
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 101,749评论 0 226
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 42,581评论 0 191
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 50,741评论 3 271
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 39,684评论 1 192
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,122评论 2 292
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 29,847评论 0 182
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 33,441评论 0 228
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 29,939评论 2 232
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,333评论 1 242
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 27,783评论 2 236
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,275评论 3 220
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,830评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,444评论 0 180
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 34,553评论 2 249
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 34,618评论 2 249