Flutter Module 工程模板深度解析:目录结构、生成机制与 add-to-app 实战
2026/9/8 23:13:51 网站建设 项目流程

Flutter Module 工程模板深度解析:目录结构、生成机制与 add-to-app 实战

【免费下载链接】flutterFlutter makes it easy and fast to build beautiful apps for mobile and beyond项目地址: https://gitcode.com/GitHub_Trending/flutter41/flutter

本文以 Flutter 官方仓库中的 Module 模板说明文档 packages/flutter_tools/templates/module/README.md 为骨架,结合同目录下的真实模板文件与 Flutter 工具链源码,系统讲解flutter create --template module生成的嵌入式(add-to-app)Flutter 工程内部被拆分成哪些子模板、每个子模板会写入磁盘的哪个位置、解决什么问题。读完本文,你将掌握 Module 工程的.android/.ios/隐藏目录结构、ephemeral(临时)与 editable(可编辑)宿主工程的区别,以及如何在 Android/iOS 宿主 App 中通过 Gradle / Xcode 消费 Flutter 视图与产物。

Module 模板在 Flutter 中的地位

在 Flutter 框架仓库中,Module 工程模板位于 packages/flutter_tools/templates/module,它与apppackageplugin等模板并列,是flutter create命令在指定--template module(对应源码中的FlutterTemplateType.module,见 packages/flutter_tools/lib/src/commands/create.dart)时渲染的一组文件集合。

Module 工程面向的是add-to-app场景:即 Flutter 代码不作为独立 App 运行,而是以「库」的形式被既有原生 Android/iOS 宿主工程引用。这一点直接体现在模板说明文档的多个论断上:

  • 模板说明称,Android 侧内容会把 Flutter/Dart 代码「包装成一个 Gradle 工程中定义的 Android library」;
  • iOS 侧内容则把 Flutter/Dart 代码「包装成可供 Xcode 工程消费」的形态;
  • common部分则负责为模块工程本身补充pubspec.yaml等 Dart 工程文件。

因此,Module 模板不生成完整的多端 App 骨架,而只关心两种平台(Android 与 iOS)。源码 packages/flutter_tools/lib/src/commands/create.dart 中同样硬编码了这一约束:The module template only supports iOS and Android,且不支持--platforms参数。

模板顶层布局总览

Module 模板在磁盘上被组织为三个顶层目录,模板说明中分别以## common## android## ios三节描述:

顶层目录模板说明中对应的产物目标作用
commonFlutter 模块工程根目录补齐 Dart 工程文件(pubspec.yaml等)
android.android/android/生成 Android library 与宿主 App 工程
ios.ios/等隐藏目录生成供 Xcode 消费的库与宿主 App 工程

需要特别注意的是,模板说明文档描述的是「每个子模板被渲染到目标工程的什么位置」这一契约,物理模板文件本身则统一存放在 packages/flutter_tools/templates/module 下。理解这一点对定位模板源码很有帮助:例如文档中 android 一节提到的library子模板,在物理磁盘上对应的是 android/library_new_embedding 目录。

下面按common → android → ios的顺序逐一展开。

common:写入模块根目录的 Dart 工程文件

模板说明用一句话概括了common的职责:

Written to root of Flutter application. Adds Dart project files includingpubspec.yaml.

common目录下的文件会直接渲染到新建 Module 工程的最外层(如my_module/),内容包括(对照 common 下的实际文件):

  • pubspec.yaml.tmpl:模块工程的 Dart 依赖清单;
  • lib/main.dart.tmpl:模块默认入口 Dart 代码;
  • test/widget_test.dart.tmpl:配套的 Widget 测试;
  • analysis_options.yaml.tmpl:静态分析配置;
  • README.md.tmpl及 IDE 工程文件模板。

pubspec.yaml是这里最有信息量的文件。查看 common/pubspec.yaml.tmpl 可以看到,普通 App 模板之外,它额外声明了一个flutter: module:段,内含三个由模板引擎注入的关键配置:

flutter: uses-material-design: true module: androidX: true androidPackage: {{androidIdentifier}} iosBundleIdentifier: {{iosIdentifier}}

模板中的注释对此有明确说明:这三项标识符不应在生成后随意改动,工具链依赖它们来保持「新增/修改资源与插件」时的一致性;同时它们与原生宿主 App 自身的标识符相互独立,二者可以相同也可以完全不同。从源码看,默认值分别取自androidPackagecom.example.<projectName>风格与应用名组合(见 packages/flutter_tools/lib/src/project.dart 中渲染 Android 模板时的androidIdentifier逻辑),iOS 侧同理由iosBundleIdentifier兜底。

android 子模板详解

模板说明中 android 一节包含五个子条目:librarygradlehost_app_commonhost_app_ephemeralhost_app_editable。这五者按职责可以分为「库工程」与「宿主工程」两条主线。

library:把 Flutter 包装成 Android Library

模板说明的核心表述:

Written to the.android/hidden folder. Contents wraps Flutter/Dart code as a Gradle project that defines an Android library. Executing./gradlew flutter:assembleDebugin that folder produces a.aararchive.

library子模板被渲染到模块工程下的.android/隐藏目录(这也是 Flutter 工具自动生成的宿主与库工程目录),它在物理模板上对应 android/library_new_embedding。目录内提供了:

  • settings.gradle.copy.tmpl:库工程的 Gradle 设置脚本;
  • include_flutter.groovy.copy.tmpl:负责把 Flutter 相关子工程注入 Gradle 构建的辅助脚本;
  • flutter.iml.copy.tmpl:IDE 模块描述;
  • src/main/AndroidManifest.xml.tmpl:库的 Manifest。

看一下渲染后的settings.gradle(模板原文见 android/library_new_embedding/settings.gradle.copy.tmpl):

// Generated file. Do not edit. rootProject.name = 'android_generated' setBinding(new Binding([gradle: this])) evaluate(new File(settingsDir, 'include_flutter.groovy'))

第一行注释与最后两行代码揭示了两个关键事实:其一,.android/内是「自动生成」产物,不应手工编辑;其二,include_flutter.groovy通过 Gradle 的动态求值机制把 Flutter 工程作为子工程挂入当前构建。完成渲染后,只要在.android/目录下执行./gradlew flutter:assembleDebug,即可产出可被宿主 App 依赖的.aar归档——这正是 Android 宿主工程消费 Flutter 视图的载体。

gradle:Gradle 样板补丁

模板说明:

Written to.android/orandroid/. Mixin for adding Gradle boilerplate to Android projects.

gradle子模板是一组「混入式」文件,用于向 Android 工程补充 Gradle 样板(如 gradle.properties.tmpl、settings.gradle.tmpl、AndroidManifest.xml.tmpl等)。它既可落到隐藏的.android/,也可落到可见的android/——具体落在哪里取决于宿主工程是临时生成还是由作者维护(见下文 ephemeral / editable 之分)。源码中_regenerateLibrary()正是把 android/gradle 渲染进.android/临时目录的(见 packages/flutter_tools/lib/src/project.dart)。

host_app_common:单 Activity 宿主壳

模板说明:

Written to either.android/orandroid/. Contents define a single-Activity, single-View Android host app with a dependency on the.android/Flutterlibrary. Executing./gradlew app:assembleDebugin the target folder produces an.apkarchive. Used with eitherandroid_host_ephemeralorandroid_host_editable.

host_app_common定义了一个「单 Activity、单 View」的最小 Android 宿主应用:它只包含一个指向 Flutter 视图的MainActivity(模板位于 host_app_common/app.tmpl/src/main/java/androidIdentifier/host/MainActivity.java.tmpl),配以基础的AndroidManifest.xml.tmpl、启动背景launch_background.xml、主题styles.xml与启动图标资源,并且依赖上面提到的.android/Flutter库工程。

它在概念上是「公共底座」,本身不决定宿主工程的存放位置。在.android/或可见的android/目录下执行./gradlew app:assembleDebug,即可得到可直接安装运行的.apk,方便在开发阶段用flutter run或原生 Gradle 直接预览模块效果。

host_app_ephemeral 与 host_app_editable:临时的还是可编辑的?

这两者分别对应模板说明中的:

Written to.android/on top ofandroid_host_common. Combined contents define anephemeral(hidden, auto-generated, under Flutter tooling control) Android host app...

Written toandroid/on top ofandroid_host_common. Combined contents define aneditable(visible, one-time generated, under app author control) Android host app...

二者的模板内容几乎一致(都是基于host_app_common叠加一份settings.gradle,分别见 android/host_app_ephemeral/settings.gradle.tmpl 与 android/host_app_editable/settings.gradle.copy.tmpl),区别只在于目标目录与归属权

宿主工程渲染目标归属与生命周期
host_app_ephemeral.android/(隐藏目录)自动生成、由 Flutter 工具链全权管理,可随时按需重建
host_app_editableandroid/(项目可见目录)一次性生成,随后交由 App 作者维护,工具链不再覆盖

ephemeral(临时)与editable(可编辑)这对术语是理解整个 Module 模板目录哲学的关键:工具链总是优先使用隐藏的临时工程来跑通「最小可运行」场景(flutter runflutter build等都依赖它),而一旦开发者在根目录手动flutter create .生成了可见的android/宿主工程(例如要接自有 Gradle 配置、改原生代码),工具链就会检测到并停止重建临时宿主壳。

这个分支逻辑在源码中有清晰体现。packages/flutter_tools/lib/src/project.dart 中ensureReadyForPlatformSpecificTooling()会先判断是否需要重新生成,然后:

  • 始终先重建库工程(渲染module/android/library_new_embeddingmodule/android/gradle.android/);
  • 仅在可编辑宿主目录(android/)不存在时,才叠加渲染host_app_common+host_app_ephemeral.android/
// Add ephemeral host app, if an editable host app does not already exist. if (!_editableHostAppDirectory.existsSync()) { await _overwriteFromTemplate(/* host_app_common */, ephemeralDirectory); await _overwriteFromTemplate(/* host_app_ephemeral */, ephemeralDirectory); }

也就是说:editable 宿主一旦出现,ephemeral 宿主便让位。同时,判定是否需要重建的条件是「.android/是否比模块根目录的pubspec.yaml更旧,或者是否早于工具链版本戳」——因此当你在pubspec.yaml中新增插件依赖后,下一次构建工具链会自动重新生成隐藏工程,这正解释了模板中「Generated file. Do not edit.」的警告。

ios 子模板详解

iOS 侧同样沿袭「库 + 宿主」的划分,模板说明分三节描述:libraryhost_app_ephemeralhost_app_ephemeral_cocoapods。值得注意的是,iOS 侧只提供 ephemeral 形态的宿主工程,这与 Android 侧同时提供 editable 形态有所差异。

library:供 Xcode 消费的包装层

模板说明:

Written to the.ios/Flutterhidden folder. Contents wraps Flutter/Dart code for consumption by an Xcode project. iOS host apps can set up a dependency to this contents to consume Flutter views.

library子模板渲染到模块工程的.ios/Flutter隐藏目录。物理文件位于 ios/library/Flutter.tmpl,包含:

  • podhelper.rb.tmpl:供 CocoaPods 集成的辅助脚本;
  • AppFrameworkInfo.plist:框架元信息;
  • 配套的README.md

与 Android 的.aar思路一致,iOS 侧的目标是让原生宿主工程能够以依赖形式消费.ios/Flutter目录中的内容,从而在自己的 Xcode 工程中嵌入 FlutterViewController。

host_app_ephemeral:无 CocoaPods 的最小 iOS 宿主

模板说明:

Written to.ios/outside theFlutter/sub-folder. Combined contents define anephemeral(hidden, auto-generated, under Flutter tooling control) iOS host app with a dependency on the.ios/Flutterfolder contents. The host app does not make use of CocoaPods, and is therefore suitable only when the Flutter part declares no plugin dependencies.

host_app_ephemeral渲染到.ios/下、但不进入Flutter/子目录,它提供了一整套完整的 Xcode 宿主工程骨架(ios/host_app_ephemeral):

  • Runner.tmpl/:含main.mAppDelegateSceneDelegateInfo.plist.tmplBase.lproj的启动与主 storyboard、Assets.xcassets图标资源等;
  • Runner.xcodeproj.tmpl/Runner.xcworkspace.tmpl/:Xcode 工程与工作区描述(含共享 scheme);
  • Config.tmpl/Debug.xcconfigRelease.xcconfigFlutter.xcconfig等配置。

一个关键的工程约束是:这个最小宿主不依赖 CocoaPods。它适合纯 Flutter 代码、没有声明任何插件依赖的情形;一旦模块引入插件,就需要 CocoaPods 来拉取与注册插件,此时应使用下一个变体。

host_app_ephemeral_cocoapods:带插件支持的必要变体

模板说明:

Written to.ios/on top ofhost_app_ephemeral. Adds CocoaPods support. Combined contents define an ephemeral host app suitable for when the Flutter part declares plugin dependencies.

host_app_ephemeral_cocoapods是在host_app_ephemeral之上叠加 CocoaPods 支持的补丁层,物理上对应 ios/host_app_ephemeral_cocoapods,其中Podfile.copy.tmpl即注入的 Podfile。

它在什么条件下被启用?看 iOS 侧的再生逻辑(packages/flutter_tools/lib/src/xcode_project.dart 中_regenerateModuleFromTemplateIfNeeded())会更清楚:与 Android 侧如出一辙,工具链先判断.ios/是否早于pubspec.yaml或早于工具链版本戳,若是则重建.ios/Flutter库内容,再在可编辑宿主不存在时渲染host_app_ephemeral,随后用hasPlugins(parent)探测模块是否声明了插件依赖:

// Add ephemeral host app, if a editable host app does not already exist. if (!_editableDirectory.existsSync()) { await _overwriteFromTemplate(/* host_app_ephemeral */, ephemeralModuleDirectory); if (hasPlugins(parent)) { await _overwriteFromTemplate(/* host_app_ephemeral_cocoapods */, ephemeralModuleDirectory); } }

据此可以得出可靠结论:模块是否使用 CocoaPods 变体,由 pubspec 依赖中是否存在插件自动决定,开发者无需手工选择。此外从源码可见,Module 的插件注册宿主在 iOS 侧指向.ios/Flutter下的FlutterPluginRegistrant(见 xcode_project.dart 中pluginRegistrantHost的取值),这也解释了为何 CocoaPods 变体对插件模块是必需项。

从模板到工程:工具链如何编排这些子模板

把以上零散子模板串起来的是 Flutter 工具链中的两套「生成/再生成」流程:

  1. 首次创建flutter create --template module my_module时,create.dart 的_generateModule()会先把module/common渲染到工程根目录;而.android/.ios/隐藏工程并非在创建时完整生成,而是由后续平台工具链按需补全(这也是它们在.gitignore语义中通常被视为「生成物」的原因)。

  2. 按需再生:每次调用构建/运行类命令时,工具链会校验隐藏目录的新鲜度。Android 侧见 project.dart 的ensureReadyForPlatformSpecificTooling()/_regenerateLibrary():删除旧的.android/,重新渲染library_new_embeddinggradle混入,必要时注入 Gradle wrapper;iOS 侧见 xcode_project.dart 的_regenerateModuleFromTemplateIfNeeded(),对.ios/执行同样的重建。

两套流程共同遵守两个判定条件(均能在源码中找到对应实现):

  • pubspec.yaml的修改时间是否晚于隐藏目录(pubspecChanged);
  • 工具链自身的版本戳是否更新(toolingChanged)。

只要二者满足其一,隐藏目录就会被整目录重建,确保原生工程永远与最新 Dart 依赖/插件声明保持一致。

实战工作流建议

基于模板契约,在实际开发中可遵循以下流程使用 Module 工程:

  1. 创建模块:在宿主工程旁执行flutter create --template module(可用--org指定组织名),得到包含pubspec.yamllib/main.dart的模块根目录,以及由工具链按需生成的.android/.ios/隐藏目录。
  2. 更新依赖:编辑pubspec.yaml增加依赖后,正常执行依赖获取与构建命令即可;.android/.ios/里的生成文件无需手工同步,工具链会依据时间戳自动重建。
  3. Android 集成产物:若要在原生 Gradle 工程中消费,可在.android/下执行./gradlew flutter:assembleDebug得到.aar;开发期预览可在宿主目录执行./gradlew app:assembleDebug得到.apk
  4. iOS 集成注意点:只要模块声明了插件,就必须依赖 CocoaPods 变体(工具链会自动处理);若模块不依赖任何插件,才可使用无 CocoaPods 的最小宿主形态,以规避不必要的 Pod 依赖。
  5. 是否落地 editable 宿主:需要深度定制原生壳(改AndroidManifest、原生代码、Gradle 配置)时,才应生成可见的android/(或对应 iOS 的可编辑工程)并交出版面给团队维护;仅用于功能验证时,保持 ephemeral 形态即可,避免与工具链的自动再生机制发生冲突。

小结:一份「写给工具链」的工程蓝图

与常见模板说明不同,templates/module/README.md 本质上是一份「面向 Flutter 工具链的渲染地图」:它不教开发者手写工程,而是精确约定每个子模板该落到哪里、解决什么问题、何时被自动重建。抓住library(产物)— host_app_common(公共壳)— ephemeral / editable(两种宿主形态)— cocoapods(插件能力开关)这条主线,再去读 templates/module 下的模板文件与 project.dart、xcode_project.dart 的再生逻辑,就能对 Flutter add-to-app 的完整运作机制建立体系化认识。

【免费下载链接】flutterFlutter makes it easy and fast to build beautiful apps for mobile and beyond项目地址: https://gitcode.com/GitHub_Trending/flutter41/flutter

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询