尧图精选

flutter_app_icon鸿蒙适配实践:一键生成多端图标

🕒 发布时间:2026/10/2 9:12:37 📁 来源:尧图网络
1. 为什么 flutter_app_icon 值得做鸿蒙适配1.1 这个库原本解决了什么问题做 Flutter 开发的人应该都经历过这种痛产品经理一句“换个图标吧”接下来就是整整半天的机械劳动。一套源图要切出 Android 的 mdpi、hdpi、xhdpi、xxhdpi、xxxhdpi还要处理 iOS 的 20pt、29pt、40pt、60pt、76pt、83.5pt 各种倍率稍微粗心一点就会漏掉某个尺寸上架审核被打回。flutter_app_icon 就是专门解决这个抱怨的第三方命令行工具它基于 Dart 编写核心逻辑很简单你给它一张 1024x1024 的源图它在本地自动完成缩放、裁剪、产出全套多端图标文件并且直接覆盖到对应平台的资源目录里。老实说这个库在 Android/iOS/Web 场景下已经相当成熟社区里很多人把它接进了自己的 Flutter 工程配合 Fastlane 或者 GitHub Actions 做版本发布。但问题来了这两年 OpenHarmony 设备越来越多鸿蒙应用开发也成为很多团队的必选项这时候 flutter_app_icon 就“哑火”了。它生成的资源目录里根本没有 ohos 这一项鸿蒙工程只能手动去切一套图标之前的自动化优势瞬间归零。我这次做的事情就是给这个老牌工具接上 OpenHarmony 的“后半程”让它能把一套源图同时输出到 Android、iOS、Web 以及鸿蒙工程目录补齐自动化视觉资产部署的最后一环。1.2 鸿蒙应用图标的规格究竟特殊在哪很多人以为鸿蒙图标无非就是“方形的 PNG 资源”直接把 Android 的 ic_launcher.png 改名拷过去就行实际上这个想法会栽大跟头。OpenHarmony 的应用图标体系跟 Android 的 legacy icon 逻辑不一样它更接近 Android 8.0 之后的 Adaptive Icon 思路但又加了自家规则。鸿蒙图标通常由两张图组成一张是前台图层也就是用户实际看到的图形主体比如一个猫头、一个盾牌、一个字母另一张是背景图层作为整个图标的底色或者场景。系统会把这两张图叠起来渲染同时在不同设备、不同桌面形态下还会做视差、缩放、遮罩处理。如果不分前景背景直接压一张整图进去轻则桌面显示裁切怪异重则华为应用市场上传时直接被驳回。加上鸿蒙对不同屏幕密度有一套自己的像素档位要求并不完全和 Android 的 mipmap 目录一一对应这就导致“复制粘贴”式的移植思路几乎走不通。1.3 官方 Flutter 工具链目前留下的空缺说到这必须提一句Flutter 官方对 OpenHarmony 的支持这几年进步很明显从最早只存在于开源社区的分支版本到后来 DevEco Studio 与 Flutter SDK 联动再到现在的 ohos 目录原生嵌入工程模板整体开发体验已经比较接近 Android/iOS 双端。但注意官方框架解决的是“Flutter 引擎能在鸿蒙上跑起来、Widget 能渲染、插件能桥接”这类基础问题它不会替你处理素材生产。也就是说你的 Flutter 工程里即使有 ohos 目录Xcode 有 AppIcon.appiconset、Android 有 mipmap 系列鸿蒙侧的图标资源仍然需要手动收集、裁剪、放路径。如果团队里只有一两个会切图的人这个环节几乎必然成为发版瓶颈。当初我接手团队的发版流水线时就眼睁睁看着同事用 PS 一张张导出鸿蒙图标导完还要手动改文件名核对尺寸既慢又容易出错。所以把 flutter_app_icon 的生成逻辑扩展出一套鸿蒙分支本质上是在填补官方的“素材生产自动化”空白属于非常典型且高 ROI 的适配工作。2. 适配之前的工程结构与环境摸底2.1 先认清 Flutter 工程在 OpenHarmony 下的目录形态动手改代码之前我建议你先把自己手上的 Flutter 工程翻出来看一遍确认它到底处于哪个演进阶段。早年做鸿蒙适配普遍做法是拉一个flutter_flutter的 OpenHarmony 分支或者把ohos目录手工塞进工程根目录里面的结构跟现在官方模板差异不小。而现阶段比较标准的 Flutter 工程在pubspec.yaml同级目录下会存在ohos/文件夹内部大体的目录结构长这样ohos/ ├── entry/ │ └── src/ │ └── main/ │ ├── module.json5 │ ├── resources/ │ │ ├── base/ │ │ │ ├── element/ │ │ │ ├── media/ │ │ │ └── profile/ │ │ └── rawfile/ │ └── ets/ └── ...其中module.json5是鸿蒙模块的配置文件类似 Android 的 AndroidManifest.xml应用图标引用关系就在这里面声明。resources/base/media是放置图标等位图资源的常用目录不过鸿蒙允许你按限定词建子目录比如media.en_US或者media.arkui只是默认工程一般只用base一套。搞清楚这个目录形态后面生成器输出文件才放得准。2.2 图标资源在鸿蒙工程里的真实映射位置这一步是关键中的关键也是我最早踩坑的地方。很多 Flutter 工程师习惯了 Android 的android/app/src/main/res/mipmap-*和 iOS 的Assets.xcassets以为鸿蒙也是类似逻辑把图标往某个目录一丢就行。实际上鸿蒙的图标资源映射核心入口在module.json5里通过abilities节点表示。举个例子应用入口模块里一般会有icon: $media:layered_icon这样的字段它指向的不是一个具体的 PNG 文件而是一个逻辑资源名称。这个layered_icon需要能在resources/base/media下找到同名资源或者通过resources/base/element里的 JSON 去解析多图层引用。如果 flutter_app_icon 生成的输出文件叫ic_launcher.png而 module.json5 指向layered_icon哪怕文件躺在正确目录里也不会生效。所以适配时我们要做的不是简单“往 ohos 目录丢几个 PNG”而是同步处理好三层关系文件生成位置、文件命名规则、module.json5 的引用指向。这三层没对齐打包能过但装到手机上图标就是默认的灰色方块排查起来很容易让人抓狂。2.3 工具链与环境版本怎么选接下来是你本机需要准备的东西。这里我列一个我实测过能稳定工作的组合不一定要求大家完全照搬但至少可以帮你少走弯路工具建议版本说明Flutter SDK3.7 及以上支持 ohos 模板越低版本对 OpenHarmony 目录支持越差Dart SDK随 Flutter 内置即可flutter_app_icon 本身是 Dart 写的直接跑脚本Node.js16 以上部分自动化流水线需要用到脚本触发DevEco Studio4.0 及以上用于最终打包 HAP 验证图标生效情况OpenHarmony SDKAPI 9 及以上API 版本影响 module.json5 字段注意区分版本选择背后有一个实际考虑flutter_app_icon 过去主要生成 Android/iOS 资源它内部对“端”的判断是枚举写死的。适配鸿蒙时如果 Flutter 版本本身不支持 ohos 目录工具就算生成了文件放进工程也未必被 DevEco 的编译链路识别。反过来用太新的 Flutter SDK 但又拉了一个老版本的分支 flutter_app_iconDart 语法兼容也可能出问题。我建议优先把自己工程的 Flutter 版本确认清楚再改代码否则容易陷入“代码没问题但构建失败”的鬼打墙。2.4 准备一份合格的源图在动手改代码之前还要确认一件事源图质量。flutter_app_icon 本质上是“缩放器”它不会帮你提升原始素材的清晰度。如果你喂给它的源图是 512x512 的 JPEG那生成鸿蒙的大尺寸图标时必然发虚如果源图带着透明通道但内容本身没有撑满安全区烘托出来的前景背景比例也会很怪。我个人的实践经验是源图至少准备 1024x1024 的 PNG并且把“视觉主体”控制在中心半径 50% 的圆形区域内。这样做不是为了迎合 flutter_app_icon而是因为鸿蒙桌面图标会在前景层做缩放和视差处理主体太靠近边缘用户转动设备或者系统渲染视差时很容易产生裁切感观感一下就廉价了。确认源图满足要求后再进入正式改造环节。3. flutter_app_icon 鸿蒙适配改造从源码到本地验证3.1 克隆仓库并定位图标生成核心逻辑万事俱备接下来进入正题。先把 flutter_app_icon 仓库克隆到本地我一般习惯用一个干净的临时目录试跑不直接动现有工程。git clone https://github.com/flutter-app-icon/flutter_app_icon.git cd flutter_app_icon打开项目目录后不用管那些 example 和多语言的 README真正要关注的是lib源码目录。核心逻辑通常在类似lib/src/icon_generator.dart或者lib/src/generator.dart的文件里里面定义了所有平台图标规格的映射关系。你可以用关键字mipmap、AppIcon、android去搜快速定位到生成目标的枚举和尺寸表。这个文件里会有一个大的 Map 或者 switch 分支分别列出 Android 的mipmap-mdpi、mipmap-hdpi等目录以及对应的像素尺寸。我们的目标很明确在这个表里加入ohos相关条目并让生成主流程在解析目标平台时认识ohos这个新成员。听起来简单但具体添加的位置和命名规则必须看懂了再动否则会碰到“生成器不认识新平台”的异常。3.2 把“生成目标”里加进 ohos注册尺寸表现在来看我具体怎么改。找到类似IconGenerator的类后里面通常会有一个类似getIconSpecs的方法返回各平台尺寸规格。MapString, Listint getIconSpecs(PlatformType platform) { switch (platform) { case PlatformType.android: return { mipmap-mdpi: 48, mipmap-hdpi: 72, mipmap-xhdpi: 96, mipmap-xxhdpi: 144, mipmap-xxxhdpi: 192, }; case PlatformType.ios: return { AppIcon.appiconset/Icon-602x.png: 120, // ... }; } }我在这里加了一个PlatformType.ohos分支尺寸表参照了 OpenHarmony 对应用图标的建议档位前景和背景各自生成 48、72、96、144、192 这五个尺寸。这么做有一个直观好处鸿蒙图标在手机、平板、折叠屏上的显示基本覆盖到了而且与 Android 的 mdpi 到 xxxhdpi 一一对应后续写脚本时心智负担最小。case PlatformType.ohos: return { foreground/ohos_icon_foreground_48.png: 48, foreground/ohos_icon_foreground_72.png: 72, // ... background/ohos_icon_background_192.png: 192, };为什么这里要区分 foreground 和 background 子目录直接拍平放在一个media目录不行吗前面说过鸿蒙系统渲染的是图层叠加。如果所有尺寸、所有图层全丢在一个目录里资源打包可以过但模块配置没法引用独立图层就算 module.json5 配好了桌面上看起来也是少了层次感。尤其是当背景是一张纯色或者渐变图、前景是透明 PNG 时只有分目录输出才能保证后期替换、换肤、主题适配更灵活。这个设计不是拍脑袋而是顺着 OpenHarmony 资源管理的习惯走的。3.3 让生成器按鸿蒙规则分前景、背景输出光有尺寸表还不够还得让实际写文件部分的代码知道生成ohos平台时要把源图略作内缩处理后写成前景层把原图或者缩放后的底色写成背景层。flutter_app_icon 底层一般是复用image包来做解码和缩放。我改造的逻辑大致是这样读取源图1024x1024 PNG。复制一份作为前景图按尺寸缩放但把画布扩大到 1.2 倍保持主体居中。这一步是模仿鸿蒙前景层的安全区处理逻辑避免生成的图标在桌面视差效果下出现过窄的安全余量。复制另一份作为背景图如果源图本身是一个带背景的完整图标就直接缩放如果源图是透明底就生成一个与背景色一致的纯色画布颜色可以在命令行参数里指定。按照ohosIconSpecs里的尺寸表循环生成所有 PNG 文件输出到ohos/entry/src/main/resources/base/media/下的对应子目录。await _generateOhosIcons( sourceImage: decodedImage, outputDir: ohosMediaDir, backgroundColor: const Color(0xFF111111), );这里我强烈建议在改造时不要去动 Android/iOS 原来的生成逻辑额外加一个独立方法处理 ohos避免破坏现有用户的使用习惯。如果你把源码推回给上游维护者也会更倾向于接受这种“新增平台分支”而不是“重构原有逻辑”的提交。3.4 命名与目录规范避免 build 阶段踩命名坑写文件这部分还有一个极其容易翻车的点资源命名。OpenHarmony 的资源命名不像 Android 那样几乎任意小写字母、数字、下划线都行它更严格。文件名不能以数字开头不要包含大写字母不能用横杠也不要出现.9.png这类 Android 独有格式。之前看到有人把生成文件命名为Icon-192.png结果 DevEco Studio 编译时提示资源名非法打包直接中断。我最终的命名方案是统一前缀ohos_icon_中缀区分foreground/background后缀是像素尺寸比如ohos_icon_foreground_192.png、ohos_icon_background_192.png。这样的好处有三个第一全部小写且无非法字符DevEco 不会有脾气第二文件名自解释后续同事看到目录不用猜第三排序规整在文件管理器里人工检查时一目了然。对应地module.json5里的图标引用也要调整。如果entry模块使用了layered_icon作为逻辑名我建议直接改成引用具体的 media 资源名避免额外维护一套element解析链。例如icon: $media:ohos_icon_foreground_192,背景层则在资源配置或者坚守默认系统背景之间取舍。如果你的前景图本身自带背景甚至可以只引用前景层、背景留空让系统垫默认色这样上架审核也不会挑毛病。3.5 本地跑通一条命令生成四端图标改造完成之后最激动人心的时刻就是用命令行实测。flutter_app_icon 通常支持通过dart run直接执行入口文件也能注册成flutter_app_icon命令。我的用法是在项目根目录准备好icon_source.png然后执行dart run flutter_app_icon --sourceicon_source.png --platformsandroid,ios,ohos执行完毕后检查目录输出。正常情况下ohos/entry/src/main/resources/base/media/下会出现我们定义的前景、背景两组文件同时原来的 android 和 ios 资源也一并更新。这一步能跑通说明本地改造基本完成。随后打开 DevEco Studio 编译一个 HAP 包安装到模拟器或者真机上观察桌面图标显示效果。这里要特别提醒模拟器上图标缓存很顽固有时候即使文件替换了、包也重装了桌面依然显示旧图标。我一般会先卸载应用、清掉 Launcher 缓存再重新安装验证否则容易误判成“生成无效”实际只是缓存没刷新。4. 在 OpenHarmony 上打造自动化视觉资产部署实战4.1 最轻量的接入方式把生成脚本挂进打包命令改造完 flutter_app_icon 之后它就不再是“一次性工具”了而应该变成你工程里员日常打包流程的一部分。最轻量的接入方式是写一个本地 Shell 脚本放在工程根目录下的tool/文件夹里每次打包前先跑一遍图标生成。#!/bin/bash set -e ICON_SOURCE${ICON_SOURCE:-assets/app_icon.png} FLUTTER_APP_ICON_CMDdart run flutter_app_icon $FLUTTER_APP_ICON_CMD \ --source$ICON_SOURCE \ --platformsandroid,ios,ohos \ --backgroundColor#111111 echo icons refreshed at $(date)然后在 DevEco Studio 的构建任务或者你脚手的 HAP 打包脚本里把这段脚本放在编译之前。这样每次更换图标素材团队只需要替换assets/app_icon.png一个文件剩下的尺寸切割、目录拷贝、命名对齐全部自动完成。我个人的体会是这个阶段不要追求“一键全自动”先把流程跑通最重要。因为一旦脚本前置到打包链路里它就成为所有开发者共享的“基础设施”如果中间出 bug比如输出目录写错、图片尺寸异常会直接卡住全团队的打包。所以初期我情愿多手动验证几轮也要保证脚本的幂等性无论跑多少遍产出的文件内容一致不会越跑越乱。4.2 在 CI/CD 流水线里跑图标生成与校验本地跑通之后就该把图标生成接入 CI/CD 了。以 GitHub Actions 为例我在流水线上增加一个 job专门负责视觉资产刷新和校验。这个 job 做的事情很单纯检查源图的哈希值是否有变化有变化才跑生成器避免多余的文件改动污染提交记录。核心思路是用git diff判断assets/app_icon.png是否被修改如果修改了就执行dart run flutter_app_icon然后把生成的ohos/、android/、ios/资源一并提交回仓库。如果源图没变直接跳过生成步骤保留上一次的资源产物。这样做的好处是不会让 CI 每次构建都产生无意义的文件变更也方便审查人员快速看出一次改动到底影响了哪些端。流水线里还应该加一个资源校验动作检查关键文件是否存在并且尺寸符合预期。我在 CI 脚本里用了 ImageMagick 的identify命令做快速核对identify -format %w x %h %f\n \ ohos/entry/src/main/resources/base/media/foreground/*.png \ ohos/entry/src/main/resources/base/media/background/*.png尺寸不对或者文件缺失CI 直接 fail不给问题图标进入正式包的机会。实测下来这个步骤特别管用能把“同事忘跑脚本直接提交代码”这类低级失误拦截住。4.3 资源缓存、增量构建与幂等性设计自动化部署过程中最容易被忽略的是缓存和幂等性。OpenHarmony 的 DevEco Studio 构建系统会缓存资源处理结果如果你只是覆盖了 PNG 文件但文件名和路径没变构建系统可能以为资源没更新直接把旧的打包进 HAP。这时候就算源图换成了新图标最终产物依然显示旧图。针对这个问题我用的手段有两个。第一生成新图标后主动 touch 一下module.json5改变文件的修改时间让构建系统意识到配置有变化进而触发资源重新解析。第二在脚本里输出一份icon_version.txt内容是一个自增序号或者源图 MD5同时把这个版本号注到模块配置里确保每次内容不同时产物也会不同。echo # $(md5sum assets/app_icon.png) ohos/entry/src/main/resources/base/media/icon_version.txt这个文件虽然不会被安装包使用但它能作为构建链路上的“噪音发生器”让 DevEco 重新评估资源目录。实际效果非常明显至少我在真机测试中没有再遇到过“图标不刷新”的情况。还有一个小细节不要在 CI 里使用当前时间作为版本号否则同一份源图会在每次构建时生成不同元数据破坏可重复构建。用源图哈希最稳因为内容不变就不应该产生新的构建差异。5. 常见问题与排查技巧实录5.1 图标生成后桌面显示的还是旧图这个坑我排了很久最后定位到两个原因。一是构建缓存这在 4.3 里已经说过通过 touchmodule.json5或者改写版本文件能强制刷新。二是设置里可能开了“默认图标”模式某些设备主题或者开发者选项会把应用图标强制成系统默认样式尤其是从 beta 版本 OpenHarmony 刷过来的设备更容易出现。验证方法也很简单去设置里搜索“图标”或者重新应用一次浅色/深色主题如果图标恢复了就说明不是资源生成问题而是系统显示问题。5.2 图标发虚、边缘锯齿大多数情况下发虚都是源图分辨率不够导致的。flutter_app_icon 再强大也只是缩放器216 像素的源图强行拉到 192 甚至 1024 档位不虚才怪。建议回炉素材换成 AI 或者矢量稿导出一份 1024x1024 的图。还有一种情况比较隐蔽源图是 JPG 格式JPG 的压缩噪点在放大后会被边缘检测放大视觉上像“毛刺”。解决方法很简单——转成无损 PNG 再喂给工具。5.3 华为应用市场上架时图标被驳回如果只是内部测试图标只要能在桌面上正常显示就行但一旦准备上架华为应用市场审核就会严格很多。我遇到过被驳回的理由是“图标未使用安全区”“图标背景层带透明像素”和“前景层内容占比过小”。这三点都跟图层规范有关。适配时最好在生成器里加一个约束生成前景图前先把源图中央 80% 区域放大到画布的 90% 以上同时确保背景层完全不透明。这套规则跟华为开发者文档里对素材的要求基本能对齐。5.4 其他高频问题速查表现象可能原因解决办法生成文件没有出现在 ohos 目录路径硬编码与工程结构不匹配检查 ohos/entry 路径是否存在建议用配置文件指定入口module.json5 引用报错文件名包含大写或非法字符统一改为小写与下划线背景层与前景层重叠后观感不佳前景图安全区预留不足生成前景时画布放大 1.2 倍主体居中构建时提示 media 资源重复旧文件未清理生成前清空目标目录或者按固定命名覆盖图标在部分设备上被过度放大裁切源图主体超出安全区回源修图把主体控制在中心圆内CI 脚本跑完 git 有大量无关 diff生成逻辑不幂等输出内容加入哈希后缀或排序输出确保不稳定时间戳不进入文件5.5 关于这套适配方案我最想说的一点说实话给 flutter_app_icon 做鸿蒙适配的过程中技术难点并不在于“Dart 代码怎么写”而在于你是否理解 OpenHarmony 的资源管理哲学图标不是一张图片而是一组图层的组合。只要抓住了前景、背景、安全区、尺寸档位和 module.json5 引用这五个关键点剩下的就是不断跑脚本、看真机效果、调整参数的体力活。经过这次改造我团队现在发版时的图标更新动作已经收敛成一行命令替换源图跑打包流水线全部端侧图标自动就位。我能明显感觉到把“视觉资产部署”这件事交给自动化之后不再依赖某个会切图的同事有空也不再担心临近发版才发现某台设备图标尺寸不对。这套流程从 OpenHarmony 到 Android、iOS 完全统一质量反而比手工切图更稳定。如果你也在做 Flutter 多端工程我强烈建议找时间梳理一下自己的资源生成链路别让图标这种看起来很小的事拖慢整个发版节奏。按我在上面的方案去改不用重构业务代码半天时间就能落地。
上一篇/下一篇内容由系统自动关联 返回资讯列表 →