题图来自pixabay stevepb
本文旨在介绍和探索flutter工程创建中的集中模式的的作用和区别, 关于native的介绍偏向iOS, 希望能对你有帮助
前言
大家在了解了flutter之后, 想小试牛刀一把的时候, 想必第一个遇到的命令就是它了
$ flutter create
对, 就是创建一个flutter工程, 当然, 若是使用Android Studio或者是VSCode这样的IDE, 也是有了可视化的GUI, 最终还是跑不掉这个命令的, 这里就有多种的创建模式了。
一起看下这个命令的参数
... 省略许多选项
-t, --template=<type> 选择一个工程的模式
[app] (默认) 生成flutter完整工程
[package] 生成一个包含dart代码的独立库
[plugin] 生成一个包含Native和Dart代码的独立库
[module] 生成一个集成的库
... 省略许多选项
其中省略了一些demo模板, 开发语言等参数, 我们可以创建4中模式的flutter工程, 应对不同的使用场景
模式简介
根据命令的参数可以看到有四种模式可以使用, 其中app模式可以说是完全的flutter工程了, 另外三种模式属于独立包的模式,
通过嵌入到flutter或native工程中来使用, 其中只有module模式是通过嵌入到native工程使用的。
从图中可以看出来, 四种模式对于开发方式上的区别:
- app模式: flutter的完整工程模式, 适合从头编写的app场景,配合plugin来解决native侧特殊功能的桥接
- package模式: 只包含dart代码部分, 可以在dart上实现组件的封装及复用,不涉及native部分的交互
- plugin模式: 专门适用于flutter与native有交互的场景, 比如获取定位等硬件功能
- module模式: 类似app模式, 为了和已有的native工程融合专用模式
Flutter的产物
怎么就跳到flutter产物了呢?
嗯, 是的, 使用flutter开发, 不得不先简单说下flutter产物, 它们关系到整个app怎么能运行在ios和android平台上。
iOS端:
- App.framework:dart业务源码相关文件
- Flutter.framework:Flutter库和引擎
- flutter_assets:Flutter依赖的静态资源,如字体,图片等
Android端:
- libflutter.so: Flutter库和引擎, 以及业务代码
- flutter_assets:Flutter依赖的静态资源,如字体,图片等
这里四种模式中的native工程和flutter产物集成的方式各有不同, 尤其是module模式集成方式不太适合开发协作, 进而衍生出的混合工程集成方案。
详解四种模式
四种模式的创建都是由flutter脚本完成的, 他们的目录结构相似, 都有一些共同的结构, 其中包括:
lib目录, 包含dart代码的目录, 初始化包含main.dart文件或者是plugin或package的入口文件pubspec.yaml为dart的包管理文件,和package.json,Podile等一样的作用,编写可以参考pubspec文件的语法pubspec.lock文件可以看出来是包管理的缓存文件, 用于锁定已安装的包的版本test文件夹, 包含了单元测试使用的文件ios或.ios文件夹包含了iOS相关的代码, 这里一个是隐藏目录,有一些配置文件不同android或.android文件包含了Android相关的代码, 同样的隐藏文件有些配置不同, 下面会详细说明example文件夹里面包含了iOS和Andriod的工程代码, 主要用于调试plugin使用
注意: .ios和.android目录是隐藏目录, 会被flutter clean等命令删除重建, 不建议在此处开发,他们只是为了运行flutter代码提供了native壳工程的能力
以下命令基于flutter环境: Flutter version 1.12.1, Dart version 2.7.0, native示例使用iOS端
app模式
$ flutter create --template=app app_project
创建完成的目录结构如下:
.
├── README.md
├── android
├── app_project.iml
├── ios
├── lib
├── pubspec.lock
├── pubspec.yaml
└── test
可以看到主工程目录是由flutter组成, 包含了ios和android两个native端的工程。 在开发上主要在lib目录里面添加dart代码, 在根目录就可以很轻松的使用flutter build命令编译出两端的产物。
这里可能有疑问, 我们在这个模式下开发完dart代码, 发布的时候还是需要两端打包发布, 那dart代码与native如何在这个模式下集成的呢?
其实不止在这个模式下, 所有模式都有集成这个过程. 只是官方给出的在不同模式下的方式不一样。
在iOS开发或打包发布过程中, 必经的步骤就是build工程, 所以flutter在build过程中做一些操作即可实现工程的自定义配置
集成方式(工程创建的时候已经配置):
- 在build Phrases中添加
flutter/packages/flutter_tools/bin/xcode_backend.sh脚本用于打包fllutter产物的framework - 配置xcode工程, 嵌入framework到工程中
- 配置xcode工程对flutter framework的引用路径
这样就在每次工程build的时候自动打包并工程依赖了flutter产物, 若iOS工程需要第三方代码库依赖, 可以手动创建Podfile进行包管理。
其他几种模式的目录中也有同样的 README.md, test等文件, 下面就不展示了, 只展示不同的目录结构
package模式
$ flutter create --template=package package_project
创建完成的目录结构如下:
.
├── lib
├── pubspec.yaml
哇, 这个目录算是最简洁的了, 没有其他的native相关的文件, 只有dart代码目录即可,其目的也是为了开发纯dart代码, 不包含native代码, 但是如何调试package的代码呢?
集成方式
可以通过另一个flutter工程, 通过pubspec文件进行依赖安装package, 本地使用 path 方式依赖即可。如果说想要在这个目录下创建一个flutter工程的环境, 即app模式的工程, 也是可以的, 但是从官方的模板来看, 不建议这么做。
package模式的工程为了共享dart组件而服务的, 所以需要有个空间可以使用这些组件, 官方共享组件库pub dev, 当我们编写完可共享的组件之后, 可以通过 pub命令来发布到网站上, 开源你的组件。
集成方式就比较简单了, 使用pubspec包管理文件添加依赖即可。
plugin模式
$ flutter create --template=plugin plugin_project
创建完成的目录结构如下:
.
├── android
├── example
├── ios
├── lib
├── pubspec.yaml
接下来是plugin模式了, 从目录可以看出来相比package模式多了native工程和示例工程用于调试代码。这个模式解决了需要flutter和native通信的情况。其中example目录是一个app模式的工程, 为了调试native代码使用。
集成方式
从目录中可以看到, flutter和native代码通过example工程组合在一起, 但是native代码如何集成到flutter的呢?
├── ios
│ ├── Assets
│ ├── Classes
│ └── plugin_project.podspec
在iOS端是通过pod库方式集成到主工程的, 当开发完成之后, plugin工程和package工程一样, 都可以通过pub命令工具发布到共享平台上, 当然也可以发布在私有平台上
module模式
$ flutter create --template=module module_project
创建完成的目录结构如下:
├── .android
├── .ios
├── lib
├── pubspec.yaml
从module工程的目录结构可以看出来有点和plugin相似, 但功能上其实和app模式工程是一样的, 都可以独立集成flutter和naitive代码, 有点类似一个小的flutter app工程, 但是module模式专门是为了和已有native工程集成使用, 也是flutter官方在后几个版本之后才新增的模式。
为什么说module专门是为了和现有工程集成使用的呢?
因为这个模板包含了很多自动化的脚本, 能帮助开发者快速的集成flutter项目到现有的native工程中, 也许这也是业界使用flutter的多数情况吧~
我们一起来看下iOS下的.ios目录结构:
├── .ios
│ ├── Config
│ │ ├── Debug.xcconfig
│ │ ├── Flutter.xcconfig
│ │ └── Release.xcconfig
│ ├── Flutter
│ │ ├── AppFrameworkInfo.plist
│ │ ├── FlutterPluginRegistrant
│ │ ├── Generated.xcconfig
│ │ ├── README.md
│ │ ├── flutter_export_environment.sh
│ │ ├── hello_module.podspec
│ │ └── podhelper.rb
│ ├── Runner
│ │ ├── AppDelegate.h
│ │ ├── AppDelegate.m
│ │ ├── Assets.xcassets
│ │ ├── Base.lproj
│ │ ├── Info.plist
│ │ └── main.m
│ ├── Runner.xcodeproj
│ └── Runner.xcworkspace
这个目录包含了Runner工程,是flutter运行的native环境, 按照官方指导的集成到native工程的方式, 需要在native工程中的Podfile文件中引入podhelper.rb文件, 调用其中的方法, 即可自动配置native工程的依赖关系, 具体步骤可以移步集成flutter module。
集成的过程其实和在app模式下的flutter工程集成一样的, 只不过整个过程通过podhelp.rb文件封装了逻辑, 其中主要的文件作用是:
Flutter/flutter_export_enviroment.sh: 导出flutter的环境变量到shell中, 为后续执行podhelp.rb等做环境变量准备Flutter/Generared.xcconfig: 自定义xcode环境变量, 为后续build flutter的framework做准备Flutter/podhelp.rb: 核心的逻辑处理, 包括在Podfile中添加flutter的依赖, 在xcode中添加上面两种配置, 以及最重要的把Fltter SDK中的打包framework的脚本注入到xcode中, 在xcode build过程中实现打包flutter, embed以及link framework
这种集成方式虽然比较方便, 但是在多人协作的app中, 尤其是对非flutter开发的同学来说, 是强依赖本地安装flutter环境的, 这种开发体验极其不好, 所以有很多混合工程的集成方案出现, 虽然原理不是很复杂, 但是也是算是工程化的一步, 这块后续会写另一篇文章详细解读。
小结
其实觉得上面啰嗦了很多, 还算是稍微全一点, 但总要总结下他们的关系, 好快速了解区别和用法。
| 工程模式 | 开发语言 | 适用场景 | native集成方式 | 发布方式 |
|---|---|---|---|---|
| app | dart, native | 偏向纯flutter工程, 少量native代码 | framework / aar | native打包 |
| package | dart | 纯flutter 组件 | pod / gradle | pub发布 |
| plugin | dart, native | fluter与native通信组件 | pod / gradle | pub发布 |
| module | dart, native | 偏向纯native工程, 少量flutter代码 | framework / aar | native 打包 |
关于flutter命令
以上,
一行fluter create命令就完成了这么复杂的过程, 可见flutter内部做了大量的工作, 那么这个生成过程是如何进行的呢?
这个要深入到flutter tools这个工具集了, 这里简要说明下调用链路:
其中
$FLUTTER_ROOT是你的电脑上安装flutter的根目录, 当然,命令行工具的执行文件目录需要在PATH下, 以便输入命令的时候能找到可执行文件
- 当我们在命令行中敲下 flutter create命令的时候, 会执行
$FLUTTER_ROOT/bin/flutter脚本 - flutter脚本会调用flutter_tools入口文件
$FLUTTER_ROOT/packages/flutter_tools/bin/flutter_tools.dart - flutter_tools入口文件调用
$FLUTTER_ROOT/packages/flutter_tools/lib/executable.dart所有fluter命令的分发器 - 由分发器分发的命令会调用
$FLUTTER_ROOT/packages/flutter_tools/lib/src/commands/*.dart这些例如 create, run等的命令执行者
至此, 这些命令调用完成。
上面是一个简单的调用链,可以看到最终命令代码也都是由dart实现的, 具体实现细节后面会继续写文章解读flutter命令行工具的实现方式。