详解Flutter工程创建的几种模式

3,595 阅读3分钟

题图来自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过程中做一些操作即可实现工程的自定义配置

集成方式(工程创建的时候已经配置):

  1. 在build Phrases中添加 flutter/packages/flutter_tools/bin/xcode_backend.sh脚本用于打包fllutter产物的framework
  2. 配置xcode工程, 嵌入framework到工程中
  3. 配置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下, 以便输入命令的时候能找到可执行文件

  1. 当我们在命令行中敲下 flutter create命令的时候, 会执行$FLUTTER_ROOT/bin/flutter脚本
  2. flutter脚本会调用flutter_tools入口文件$FLUTTER_ROOT/packages/flutter_tools/bin/flutter_tools.dart
  3. flutter_tools入口文件调用$FLUTTER_ROOT/packages/flutter_tools/lib/executable.dart所有fluter命令的分发器
  4. 由分发器分发的命令会调用$FLUTTER_ROOT/packages/flutter_tools/lib/src/commands/*.dart这些例如 create, run等的命令执行者

至此, 这些命令调用完成。

上面是一个简单的调用链,可以看到最终命令代码也都是由dart实现的, 具体实现细节后面会继续写文章解读flutter命令行工具的实现方式。