- 跨平台
- 前端
- 移动开发
- OpenHarmony
【免费下载链接】flutter_flutter
本仓库是 Flutter SDK 与 Flutter Engine 的 OpenHarmony 适配版本,由 CPF-Flutter 团队维护。开发者可使用熟悉的 Flutter 技术栈开发 OpenHarmony 应用,3.35.7 及以后的适配版本可基于本仓库源码构建支持 OpenHarmony 的 Flutter Engine。
本文面向 flutter_flutter(Flutter 的 OpenHarmony 适配版本)的贡献者与二次开发者,系统讲解flutter工具模板中图片资源的维护机制:为什么图片不能直接提交进主仓库、flutter_template_imagesPub 包在其中扮演的角色、以及新增/更新模板图片时需要在flutter/packages与flutter/flutter两个仓库分别执行的完整操作步骤。读完本文,你将掌握.img.tmpl占位符与template_manifest.json清单的配合方式,并能独立完成一次模板图片的发布与回滚。
为什么模板图片不直接入库
flutter工具在生成新项目(flutter create)时,会向目标工程写入大量模板文件,其中包含各平台的图标、启动图等二进制资源(如 iOS 的AppIcon、macOS 的应用图标、Windows 的app_icon.ico、Web 的 maskable icon 等)。这些图片资源并没有直接存放在 flutter_flutter 主仓库中,而是统一维护在flutter_template_images这个 Pub 包内。
这一设计有两个直接原因:
- Presubmit 检查禁止二进制文件入库。主仓库的静态检查
verifyNoBinaries(见 dev/bots/analyze.dart)会逐个读取 Git 跟踪文件并以 UTF-8 解码,凡无法解码为合法 UTF-8 的内容都被判定为二进制而报错。该检查的错误提示中明确写着:All files in this repository must be UTF-8. In particular, images and other binaries must not be checked into this repository,并直接引用本指南作为解决方案。 - 仓库体积与升级成本。二进制文件会显著增大仓库体积,且每次更新都会产生巨大的 diff。flutter_flutter 仓库会随版本被广大开发者同步,执行
flutter upgrade时需要对整个 Git 历史做增量同步,体积膨胀会直接拖慢所有人的升级体验。而从 Pub 拉取一个包只需下载对应版本内容,不涉及 Git 历史的全量同步,明显更轻量。
从本仓库的实际结构也能印证这一点:以 packages/flutter_tools/templates/app/ios.tmpl/Runner/Assets.xcassets/AppIcon.appiconset 为例,目录下只有Contents.json是真实文件,而Icon-App-20x20@1x.png.img.tmpl、Icon-App-1024x1024@1x.png.img.tmpl等所有图标都只是零字节占位符,真正的内容在flutter_template_images包中。
双仓库工作流总览
新增或更新模板图片需要同时提交两个补丁(patch),分别落在两个仓库:
- flutter/packages 仓库:修改
flutter_template_images包,把真实图片文件放入其templates目录,并发布新版本到 Pub。 - flutter/flutter 仓库(即本仓库):把依赖版本提升到新发布的版本,同步添加占位符与清单条目,让
flutter工具在生成项目时能正确解析到新图片。
flutter_template_images包内的templates目录布局与 flutter_flutter 仓库中flutter_tools包下的 templates 目录完全一致:先向flutter_template_images提交真实图片,再回滚进主仓库,保证两处路径一一对应。回滚时,flutter_tools包中的占位文件只是同名零字节文件(文件名含.img.tmpl后缀),真实二进制仍留在 Pub 包里。
第一步:向 flutter_template_images 添加资源
1. 分叉 flutter/packages 仓库
在 GitHub 上 fork flutter/packages 仓库(若未配置 GitHub 亦可参考该仓库的本地镜像流程),在本地检出后进入packages/flutter_template_images目录操作。
2. 按目标路径放置图片
将图片放到templates目录下与 flutter_flutter 主仓库对应的相对路径。命名规则有三点要求:
- 文件名必须与
flutter工具生成项目后的最终文件名完全一致,因为模板渲染时不会做任何改名; - 不要包含
.img.tmpl后缀——该后缀只出现在主仓库的占位符中,真实图片文件用的是最终名,例如主仓库占位符是app_icon.ico.img.tmpl,包内真实文件就叫app_icon.ico; - 目录层级要与
flutter_tools/templates严格对齐,例如 iOS 图标要放在templates/app/ios.tmpl/Runner/Assets.xcassets/AppIcon.appiconset/下。
3. 升级 pubspec.yaml 版本号
编辑该包的pubspec.yaml,版本号为major.minor.patch三段式,规则是:
- 新增图片资源:递增 minor 版本(如
5.0.0→5.1.0); - 仅更新已有图片:递增 patch 版本(如
5.0.0→5.0.1)。
这个规则与本仓库中flutter_template_images的 pin 位置相互印证:主仓库pubspec.yaml中声明flutter_template_images: 5.0.0(见 packages/flutter_tools/pubspec.yaml),同时手工 pin 集中在kManuallyPinnedDependencies表中,注释写明Must always exactly match flutter_tools template(见 packages/flutter_tools/lib/src/update_packages_pins.dart)。
4. 更新 CHANGELOG.md
在包的CHANGELOG.md顶部为本次版本新增一个标题条目,并添加一行描述变更内容。格式遵循 docs/ecosystem/contributing/README.md#changelog-style 的规范(该目录页同时说明了 packages 仓库采用"持续发布"模型、绝大多数发布型变更都必须更新版本号与 CHANGELOG 的纪律)。
5. 提交并发送 Pull Request
提交改动(建议仅包含图片、版本号、CHANGELOG 三类文件),推送后发送 PR。合入后,post-submit 作业会自动把新版本发布到 Pub,无需手动执行发布命令。你可以在flutter_template_images包页面(Pub 上的Versions标签页)确认新版本是否已成功发布——只有发布成功后,主仓库才能依赖到它。
第二步:将变更回滚进 flutter_flutter 主仓库
新版本在 Pub 发布后,开始在 flutter_flutter 仓库中回滚。
1. 分叉 flutter/flutter 仓库并更新依赖 pin
fork flutter_flutter 仓库后,需要修改依赖版本声明。原指南指向update_packages.dart,而在当前仓库中,手工固定(pin)的依赖统一集中在 packages/flutter_tools/lib/src/update_packages_pins.dart 的kManuallyPinnedDependencies表中:把flutter_template_images的条目从旧版本改为新版本。同时 packages/flutter_tools/pubspec.yaml 中的依赖声明也会随之更新。
需要特别强调的是,该表中的 pin必须是精确版本而非版本范围(源码注释明确指出 ranges 会使 CI 随机失败并可能让用户无法升级 Flutter),因此这里必须写入与包版本完全一致的精确版本号。
2. 运行依赖更新命令
在仓库根目录执行:
flutter update-packages --force-upgrade该命令会基于kManuallyPinnedDependencies重新解析并锁定额外依赖。从 packages/flutter_tools/lib/src/commands/update_packages.dart 的源码可以看到,--force-upgrade是UpdatePackagesCommand的核心旗标之一;文件头部的注释还说明,Pub 包的例行升级由flutter-pub-roller-bot通过flutter update-packages --force-upgrade自动完成,人工执行时同样走这条命令。
3. 添加零字节占位文件
对于你在flutter_template_images中新增的每一个图片文件,都在flutter_tools包的 templates 目录的对应位置放一个零字节文件,文件名加上.img.tmpl后缀。例如新增 iOS 启动图,就在templates/app/ios.tmpl/Runner/Assets.xcassets/LaunchImage.imageset/下创建LaunchImage.png.img.tmpl。
零字节占位的好处是:不携带任何二进制内容,能通过 UTF-8 检查;模板引擎看到.img.tmpl后缀时也不会对其做 mustache 渲染,而是从图片包中复制真实文件覆盖它。
4. 更新 template_manifest.json
编辑 packages/flutter_tools/templates/template_manifest.json,在files数组中为每个新增文件添加一条记录,路径必须包含.img.tmpl后缀。该清单文件的_comment字段说明了对应关系:Files ending in .img.tmpl correspond to files checked in to the flutter_template_images package ... located at the same path, excluding the .img.tmpl suffix.
清单的作用在 packages/flutter_tools/lib/src/commands/create_base.dart 中体现:_computeTemplateManifest()读取该 JSON,把所有files条目解析为绝对路径 URI 集合;随后 packages/flutter_tools/lib/src/template.dart 在遍历模板目录时,会跳过清单中不存在的文件,从而清理掉flutter_tools目录里的过期文件。这保证了"清单驱动模板"的一致性。
5. 测试运行并提交 PR
提交改动后,先在本仓库内实际执行一次模板生成做冒烟测试,确认新图片能被flutter工具正确产出到生成的项目中。验证无误后再发送 PR。若 PR 中缺少占位符或清单条目,CI 会拦截:集成测试 packages/flutter_tools/test/integration.shard/template_manifest_test.dart 会递归扫描templates目录中的所有文件,并与template_manifest.json声明的清单求差集,任何"存在但未声明"的文件都会导致manifest and template directory should be in-sync断言失败。
模板引擎如何消费图片资源
理解.img.tmpl的语义对排查问题很有帮助。模板渲染的核心实现在 packages/flutter_tools/lib/src/template.dart,其类注释定义了整套后缀约定:
| 后缀 | 含义 |
|---|---|
.tmpl | 需要 mustache 渲染后写出 |
.copy.tmpl | 无需渲染,直接按原样复制 |
.img.tmpl | 从flutter_template_images包复制对应图片 |
.test.tmpl | 仅当启用--implementation-tests时才包含 |
在渲染流程(Template.render,见 template.dart)中,凡源文件以.img.tmpl结尾,引擎会遍历imageSourceDirectories,找到去掉.img.tmpl后缀后的相对路径对应的真实图片文件,直接copySync到目标位置;若找不到则抛出Image File not found的 ToolExit。目标文件路径在写出前会把.tmpl、.copy.tmpl、.img.tmpl、.test.tmpl后缀全部剥掉(见 template.dart),所以最终工程里出现的是干净的真实文件名。
图片目录的定位逻辑在TemplatePathProvider.imageDirectory(见 template.dart):它读取flutter_tools/.dart_tool/package_config.json,按包名flutter_template_images找到packageUriRoot,取其父目录下的templates目录作为图片根目录。换言之,图片解析完全依赖 Dart 包配置——这也解释了为什么主仓库必须先升级依赖并重新执行update-packages,否则新图片根本无法被解析。同样的思路也出现在 iOS 打包逻辑中:IOSApp注释写明"Template asset's images are in flutter_template_images package"(见 packages/flutter_tools/lib/src/ios/application_package.dart)。
本仓库的适配现状与验证线索
作为 OpenHarmony 适配版本,flutter_flutter 保留了整套图片资产管理机制,同时扩展了 OpenHarmony 模板。可以从以下几处快速验证当前状态:
- 依赖 pin:
flutter_template_images: 5.0.0同时出现在 pubspec.yaml 与 update_packages_pins.dart 中,且注释强制要求与模板严格同步。 - 占位符分布:
find可列出 packages/flutter_tools/templates 下全部.img.tmpl占位符,覆盖 iOS(Icon-App-*、LaunchImage*)、macOS(app_icon_*)、Windows(app_icon.ico.img.tmpl)、Web(Icon-maskable-*.png.img.tmpl)等平台。 - 清单文件:template_manifest.json 中
templates/app/ohos.tmpl/...、templates/module/ohos/...、templates/plugin/ohos.tmpl/...等 OpenHarmony 模板条目已被纳入files数组,与适配版模板共存。 - 渲染过滤:在 template.dart 的
renderPath中,ohos与android、ios、web、linux、macos、windows并列,只有创建 OpenHarmony 工程(--platforms=ohos场景)时ohos.tmpl目录下的内容才会被展开,说明 OpenHarmony 模板已深度融入同一套模板管线。
从源码结构还可以看到,OpenHarmony 模板目录(如templates/app/ohos.tmpl/AppScope/resources/base/media/app_icon.png、entry/src/main/resources/base/media/icon.png)当前以真实 PNG 文件直接放在templates目录下,并由清单直接列出(未采用.img.tmpl占位方式),与 iOS/macOS 等平台的占位符策略有所不同——这是适配版当前的实际情况,后续若要统一管理 OH 图标资源,可参照本文工作流迁移到flutter_template_images。
小结
模板图片资产的维护是一条清晰的"包内放真图、仓库放占位"的双仓库流水线:
- 在
flutter_template_images(flutter/packages)按最终文件名放置真实图片,按"新增 minor / 更新 patch"规则升级版本,更新 CHANGELOG,合入 PR 后由 post-submit 自动发布到 Pub; - 在 flutter_flutter 主仓库更新精确版本 pin(集中在
kManuallyPinnedDependencies),执行flutter update-packages --force-upgrade,为每张图片添加同名零字节.img.tmpl占位符,并在 template_manifest.json 中登记清单条目; - 依赖
template_manifest_test.dart与verifyNoBinaries等 CI 检查兜底,确保清单与目录同步、仓库不混入二进制。
掌握这套流程后,无论是为 flutter_flutter 的 OpenHarmony 模板补充图标,还是为既有平台模板替换应用图标,都能以最小的仓库体积代价、最快地让flutter create生成出带新资源的工程。
- 跨平台
- 前端
- 移动开发
- OpenHarmony
【免费下载链接】flutter_flutter
本仓库是 Flutter SDK 与 Flutter Engine 的 OpenHarmony 适配版本,由 CPF-Flutter 团队维护。开发者可使用熟悉的 Flutter 技术栈开发 OpenHarmony 应用,3.35.7 及以后的适配版本可基于本仓库源码构建支持 OpenHarmony 的 Flutter Engine。
相关推荐
Espanso资源管理终极指南:高效处理图片、模板与资产的完整攻略
Espanso资源管理终极指南:高效处理图片、模板与资产的完整攻略 Espanso作为一款跨平台文本扩展工具,不仅能帮你快速输入常用文本,还能高效管理各类资源。
桌面应用CLIFlutter 模板镜像资源管理指南:flutter_template_images、.img.tmpl 占位符与模板清单机制
Flutter 模板镜像资源管理指南:flutter_template_images、.img.tmpl 占位符与模板清单机制 flutter create 生
跨平台移动开发前端UI组件桌面应用Novu 仓库并行实现工作流:基于 Git worktree 的垂直切片(Vertical Slice)多 Agent 协作指南
Novu 仓库并行实现工作流:基于 Git worktree 的垂直切片(Vertical Slice)多 Agent 协作指南 本文讲解 Novu 开源仓库内
后端消息路由前端通信AI Agent
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考