项目结构设计:原生工程与Flutter模块的目录组织方式
聊到项目结构,我得先说说自己的血泪史。几年前我第一次把Flutter模块塞进一个老安卓项目时,目录乱得像一锅粥——Dart代码和Java文件混在一起,Gradle配置互相打架,连.gitignore都不知道该写什么。后来我花了整整两天重构结构,才明白一个道理:项目结构不是摆设,它是团队协作的骨架。
今天咱们就掰开揉碎,聊聊原生工程和Flutter模块到底该怎么组织。说白了就两个方向:单工程方案和多工程方案。各有各的适用场景,选错了后面有你受的。
一、单工程方案:Flutter模块嵌入原生项目
单工程方案,就是把Flutter模块当作一个普通模块,直接放在安卓项目的根目录下。我早期做混合开发时,这是最常用的方式。
典型的目录结构长这样:
MyApp/
├── android/ # 原生安卓工程
│ ├── app/
│ │ ├── src/
│ │ └── build.gradle
│ ├── settings.gradle
│ └── build.gradle
├── flutter_module/ # Flutter模块
│ ├── lib/
│ │ ├── main.dart
│ │ └── pages/
│ ├── pubspec.yaml
│ └── build.gradle
├── ios/ # iOS工程(略)
└── README.md
这种结构的好处很明显:简单直接。你不需要操心多仓库的版本管理,代码都在一个项目里,IDE打开就能跑。我在一个中小型项目里用过这种方案,团队只有5个人,开发效率确实高。
但坑也不少。我曾经遇到过一个场景:安卓团队和Flutter团队并行开发,结果Flutter模块的pubspec.yaml一改,安卓那边就得重新sync Gradle。更头疼的是,如果你们用Git做版本管理,这个仓库会越来越大——Flutter的编译产物动不动就几百兆。
适用场景:
- 团队规模小(10人以内)
- 项目初期,快速验证可行性
- Flutter模块改动频率低
注意:单工程方案下,Flutter模块的build.gradle需要和安卓主工程的Gradle版本保持一致。我见过有人因为版本不一致,编译时直接报"Could not resolve all artifacts",排查了半天才发现是Gradle版本冲突。
二、多工程方案:原生与Flutter独立仓库
多工程方案,说白了就是把Flutter模块抽出来,作为一个独立的Git仓库。原生工程通过依赖方式引入Flutter模块。这种方式在大型项目中更常见。
目录结构变成这样:
# 原生安卓仓库
NativeApp/
├── app/
├── settings.gradle
└── build.gradle
# Flutter模块仓库
FlutterModule/
├── lib/
├── pubspec.yaml
├── build.gradle
└── .gitignore
原生工程通过settings.gradle引用Flutter模块:
// settings.gradle
include ':app'
setBinding(new Binding([gradle: this]))
evaluate(new File(
settingsDir.parentFile,
'FlutterModule/.android/include_flutter.groovy'
))
我个人习惯用多工程方案,尤其是团队超过15人时。为什么?解耦。安卓团队和Flutter团队可以各自独立开发、独立发布,互不干扰。我曾经在一个项目里,安卓团队用Kotlin写业务逻辑,Flutter团队用Dart写UI组件,两个仓库各自有CI/CD流水线,互不阻塞。
但多工程方案也有代价:集成成本高。每次Flutter模块发布新版本,安卓团队都得手动更新依赖版本。如果你们没有完善的版本管理机制,很容易出现"安卓那边还在用旧版Flutter,但Flutter团队已经改了接口"的尴尬局面。
我的建议:如果选多工程方案,一定要用语义化版本控制(SemVer)。Flutter模块的每个版本都要有清晰的changelog,安卓团队升级时能明确知道改了啥。我曾经吃过亏——Flutter团队偷偷改了一个接口参数类型,安卓那边没注意,上线后直接崩溃。
三、两种方案的对比与选择
我整理了一张对比表,方便你快速决策:
| 维度 | 单工程方案 | 多工程方案 |
|---|---|---|
| 团队规模 | 小团队(≤10人) | 中大型团队(>10人) |
| 开发效率 | 高,无需跨仓库 | 中,需要版本管理 |
| 编译速度 | 慢,全量编译 | 快,增量编译 |
| 版本管理 | 简单,单仓库 | 复杂,多仓库 |
| CI/CD | 简单,单流水线 | 复杂,多流水线 |
| 适用阶段 | 项目初期/原型验证 | 项目成熟期/持续迭代 |
你想想看,如果你的项目还在MVP阶段,团队就两三个人,用多工程方案纯粹是给自己找麻烦。反过来,如果项目已经上线,团队分工明确,单工程方案会变成瓶颈——每次合并代码都像在打仗。
四、混合方案:折中之道
有没有两全其美的办法?有。我最近在做一个项目时,用了混合方案:
- 开发阶段用单工程方案,方便快速调试
- 发布阶段用多工程方案,保证版本隔离
具体做法是:在原生工程里通过Git Submodule或Android Studio的Module依赖,把Flutter模块作为子模块引入。开发时直接修改子模块代码,发布时子模块独立打Tag。
嗯,这里要注意:Submodule虽然好用,但团队成员如果不熟悉Git操作,容易搞乱。我建议在团队内部写一份Submodule操作指南,把常见的pull、push、merge步骤写清楚。
五、避坑指南
最后分享几个我踩过的坑:
- 不要混用Gradle版本:Flutter模块的Gradle版本必须和安卓主工程一致,否则编译报错。我曾经因为这个问题折腾了一下午。
- 注意.gitignore:Flutter的build目录、.dart_tool目录、.packages文件都不要提交到Git仓库。我见过有人把整个Flutter编译产物提交了,仓库直接膨胀到2GB。
- 统一代码风格:不管用哪种方案,建议在项目根目录放一个.editorconfig文件,统一缩进、换行符等。否则安卓团队用4空格,Flutter团队用2空格,代码review时全是格式问题。
核心原则:项目结构是为团队协作服务的,不是为技术炫技的。选方案时,先问自己三个问题:团队多少人?项目到什么阶段?Flutter模块改动频率高不高?答案清楚了,方案自然就出来了。
好了,项目结构这块就聊到这儿。记住,没有银弹,只有最适合你团队的方案。下一章咱们聊聊Flutter模块的构建配置,到时候我会分享一些Gradle脚本的骚操作。
公众号:蓝海资料掘金营,微信deep3321