Flutter组件化

背景

此篇文章,主要针对想要在原有Native工程的基础上集成Flutter的需求,所提供的混编方案的探讨。

Flutter 官方已经给出了混编方案:https://github.com/flutter/flutter/wiki/Add-Flutter-to-existing-apps#ios 这种方案有 优点 ,也有 缺点

1. 官方方案的优缺点

(1)优点:
  • 不需要每次 Run 起来之后,先进行 同步flutter代码(组件化Flutter后,因为组件化后flutter代码已经变为framework,所以每次进来需要先热更新同步代码)
  • 不需要单独搞一个组件进行集成,管理组件的版本,发布等。
(2)缺点:
  • 会非常耦合工程,需要修改工程配置,添加 BUILD PHASE 调用 flutter 中 xcode_backend.sh 脚本 去编译 Flutter。
  • 如果使用pod管理,那么还需修改xcconfig配置。
  • 因为需要调用 Flutter 的编译脚本,所以这种方式集成后,团队内所有组员电脑和打包机,都必须安装Flutter环境才能编译成功。

2. Flutter 组件化混编方案

(1)优点:
  • 不需修改 原有 xcconfig 配置。
  • 不需要添加 Run Script 脚本。
  • 运行不需要依赖 Flutter 环境。
(2)缺点
  • 需要单独管理一个 flutter私有索引库。
  • 开发加载 Flutter 页面 首次需要热更新 进行刷新同步 Flutter 代码。
(3)混编方案 实现核心思想
  • 通过查看 Flutter 编译脚本 xcode_backend.sh 和 测试单独引入编译产物,发现其实 只要拥有 Flutter 的编译产物 ,项目就可以接入 Flutter 的功能。
  • 所以说只要把Flutter编译好的产物,放在工程里,那么就无需配置每次调用 xcode_backend.sh 脚本,也无需强耦合Flutter环境,不需要所有组员安装Flutter环境,只需要有发布开发需求的同学进行安装即可。
  • 这就是Flutter组件化的实现核心点。
(4)Flutter 核心编译产物
  • App.framework:dart业务源码相关文件,在 Debug 模式下就是一个很小的空壳,在 Release 模式下包含全部业务逻辑。
  • flutter_assets:Flutter依赖的静态资源,如字体,图片等。
  • Flutter.framework:Flutter库和引擎。

目录

  • Flutter组件化 - 混编方案
  • Flutter组件化 - 断点调试
  • Flutter组件化 - 发布更新
  • Flutter组件化 - 一些坑点

一、Flutter组件化 - 混编方案

1. Git仓库存放 - 示例说明

主要分为3个仓库,分别存放Native项目、Flutter 工程源码、Flutter 编译产物私有pod库。
flutter 工程创建,使用 flutter create -t module my_flutter 命令

2. 项目目录 - 示例
  • Flutter_iOS :iOS开发主项目。
  • flutter_library :Flutter 项目的开发源码。
  • FlutterSDK :Flutter 源码的编译产物,所构建的私有 pod 库。
3. 混编方案说明
  • 根据 只要拥有 Flutter 的编译产物,项目就可以接入 Flutter 的功能 的核心思想,我们如果进行组件化Flutter混编,那么大概思路是:
有组件化环境 - 混编方案说明

(1)在 flutter 项目目录下,执行 flutter build ios 针对 Flutter 项目进行编译打包,生成 Flutter 编译产物。

Flutter 的产物分为两种模式,一个是 Debug 模式,采用 JIT(Just In Time)的方式,好处是可以支持热更新,方便调试,,但是性能比较慢。
另一种是 AOT(Ahead Of Time)release 模式,好处是性能比较好。

通过 flutter build ios --debug 可打包出 Debug 下的 Flutter 编译产物。

flutter build ios 命令依赖于 Flutter 生成的 Runner 工程,所以要确保 Runner 工程能够编译成功,这样才能生成 flutter 编译产物。如果遇到编译失败,可以检查下 bundle id 修改一下,使用自己的证书。 如下图示例:

(2)针对编译产物,制作 Flutter SDK 私有库, podspec 指定 App.framework、engine、flutter_assets 路径。

# podspec 有省略
Pod::Spec.new do |s|
  s.name         = "FlutterSDK"
  s.vendored_frameworks = 'Framework/*.framework', 'Framework/engine/*.framework'
  s.resources = 'Framework/flutter_assets'
end

(3)上传 Flutter SDK 私有库项目到云端私有pod索引库。(如何制作私有 pod 索引库,可搜索查看相关资料,这里不细说了)

(4)iOS 主项目指定 Podfile ,拉取云端Flutter私有库到本地。

没有组件化环境 - 混编方案说明
  • 没有组件化环境的项目,并且不会建立私有索引库。
  • 那么只有手动 执行 flutter build ios 命令后,将编译产物手动拖拽到iOS项目中。
4. 最后效果

如下图,可以看到最终工程只引用了一个私有 pod 库。

总结

  • 对 flutter 项目执行 flutter build ios 命令,生成编译产物。
  • 针对编译产物,制作为私有 pod 库。
  • 主工程通过 cocoapods 引入私有 pod 库。

二、Flutter 组件化 - 断点调试

因为是编译后的资源接入,我们还需要保证 Flutter 开发的同学可以正常调试。

目录

  • 单独运行 Flutter 工程调试
  • 同时调试 iOS 和 Flutter(不支持断点)
  • 同时调试 iOS 和 Flutter(支持断点)
注意点:
  • 确保,已经安装 Android Studio(用于打开 Flutter 工程)
  • 确保,项目中依赖的 Flutter 打包出来的是 Debug 版本,Release 版本是无法热更新和调试的(使用 flutter build ios --debug 打包 debug 版本)
1. 单独运行 Flutter 工程调试 (只适合和 Native 没有太多关联的工程,比较少用)

使用 Android Studio 打开 Flutter 工程目录

选择好真机或者模拟器,然后点击 Run 按钮

这样 Run 起来后,我们就可以在 Flutter 项目中打断点调试。 这种方法 只适合和 Native 没有关联的工程,比较少用。

2. 同时调试 iOS 和 Flutter(不支持Flutter断点的方式)

这种方法,需要先打开 Xcode 运行到 Flutter 页面,再进行附加 Flutter 端口号。

  • 使用 Xcode 打开 iOS 项目,运行起 Flutter 页面。

  • 会发现会输出一行日志,其中有一个端口号我们记录下来,例如:

flutter: Observatory listening on http://127.0.0.1:60455/
  • 然后 使用 Android Studio 打开 Flutter 项目(不点击运行),在 底部的 终端框中输入 flutter attach --debug-port=60455,端口号替换为xcode 日志输出的端口号。

Gif 演示

总结

  • 先打开 iOS 工程,运行起 Flutter 页面,得到一个日志输出的端口号。
  • 然后 Android Studio 打开 Flutter 工程,在底部终端处输入 flutter attach --debug-port=日志输出端口号,然后附加成功即可。
  • 注意点:附加成功后,在终端处,按小写 r 只会刷新有有修改的文件,按大写的 R 会全部刷新, 如果使用组件化的话,每次进入 Flutter 页面,Android Studio 附加成功后,都要先按大写 R 全部刷新一次 同步到最新代码。否则还会显示旧的页面。
3. 同时调试 iOS 和 Flutter(同时支持Flutter 和 Xcode断点的方式)

这种方法,需要先打开 Android Studio 选择 Attach Debugger to Android Process 等待 Flutter 页面连接,然后在 iOS 端,运行到 Flutter 页面,Android Studio 就会附加成功。

  • 首先打开 Flutter 工程,直接点击 Attach Debugger to Android Process,然后会等待 Flutter 页面连接。

  • 然后运行 iOS 工程,进入 Flutter 页面。

  • 然后就会发现 Android Studio 已经显示在 同步文件了( Syncing files to device 张大森的 iPhone...

  • 同步完成即连接成功。

注意坑点

  • 我们可以看到 闪电符号 Flutter Hot Reload 和 返回绿色符号 Flutter Hot Restart
  • Flutter Hot Reload 为局部刷新,比如某个文件有改动,才会同步刷新此页面。Flutter Hot Restart可以理解为全部刷新,在 Android Studio 面板上也有对应按钮,相应也有对应快捷键。
  • 按照 Flutter 组件化的开发方式,我们首次附加连接成功后,一定要遵循一个步骤,先 点击 Flutter Hot Restart 进行全部刷新,再点击 Flutter Hot Reload 局部刷新。因为本人发现,如果最后一次刷新点击的是 Flutter Hot Restart 按钮,那么发现断点会不生效,只有点击 Flutter Hot Reload 后 触发的断点才会生效。

演示 Gif

打断点.gif

总结

  • Android Studio 打开 Flutter 工程 ,点击 Attach Debugger to Android Process
  • 然后运行 Flutter 页面。
  • 然后点击 Flutter Hot Restart (同步最新 Flutter 代码),在点击 Flutter Hot Reload 确保断点能够生效。

三、Flutter组件化 - 发布更新

发布大概流程

(1)对 Flutter 工程 执行 flutter build ios 或 flutter build ios --debug 生成编译产物。
(2)把编译产物复制移动到 Flutter 私有库目录下。
(3)打包 上传更新私有库内容。
(4)主工程拉取最新版本。

版本更新说明
  • 开发期间:基本只用热更新进行开发代码。
  • 发布版本:一般可在上线前进行发布,所以组件版本更新,用的比较少。

四、一些坑点

(1)FlutterViewController 不释放
  • 加载 Flutter 页面后,返回后,VC 不会释放。闲鱼有大神研究过这个问题,不过目前我们没有找到解决方案去释放 VC。

  • 我们使用 单例持有了 VC ,只能做到不每次进行叠加内存,不重新创建。每次进入 Flutter 页面都先重置一下。

  • 参考文章:https://www.jianshu.com/p/9ff7a9a5dfec

(1)不支持x86_64
  • 可以用xcode跑下,生成App.framewok,然后 lipo 命令合并下。
  • 我们目前不支持模拟器,这种方案没有进行测试。

其它

  • 如果有更好的调试方法,坑点解决方法,混编方法,欢迎交流反馈下。
下一篇文章 (主要涉及到 Flutter 开发的一些知识)
  • 教你 Flutter 如何与原生进行交互 待更新

参考文章