茶器艺科智造HarmonyOS应用实战-11-AppScope与entry各放一套layered_image,改图标怎样避免只生效一半:用资源清单校验入口
茶器艺科智造HarmonyOS应用实战-11-AppScope与entry各放一套layered_image,改图标怎样避免只生效一半:用资源清单校验入口
产品把新图标交给开发,开发在 AppScope/resources/base/media 替换了 foreground.png 和 background.png,桌面图标看起来已经变化;下一次冷启动却仍出现旧图,或者系统设置页、任务中心与启动窗展示不一致。原因通常不是“系统缓存玄学”,而是同一个工程里存在多个资源所有者和多个入口,只改了一份同名文件无法覆盖全部场景。
茶器艺科智造当前同时在 AppScope 与 entry 放置 layered_image.json、background.png、foreground.png;EntryAbility 还有 startIcon.png,HSP 页面还有 splash_screen.png。本文以 master@f671fcd 的真实文件为依据,并把扩展名、文件头、尺寸、字节数和 SHA-256 一起纳入清单。

这次审计发现一个比“重复文件”更具体的事实:六个扩展名为 .png 的光栅文件,内部都是 JPEG/JFIF 数据,而且六份内容完全相同。它不一定立即导致工具链失败,但足以说明仅凭文件名判断格式和角色不可靠。


一、应用图标和 Ability 图标来自不同资源根
AppScope/app.json5 的 icon 指向 $media:layered_image,资源解析入口位于 AppScope/resources/base/media。entry/src/main/module.json5 的 EntryAbility.icon 也指向 $media:layered_image,但它由 entry 模块自己的资源目录提供。
// AppScope/app.json5
{
"app": {
"icon": "$media:layered_image",
"label": "$string:app_name"
}
}
// entry/src/main/module.json5
{
"module": {
"abilities": [{
"name": "EntryAbility",
"icon": "$media:layered_image",
"startWindowIcon": "$media:startIcon"
}]
}
}
两个引用字符串看起来一样,所有权却不一样。HarmonyOS 资源引用要结合声明所在模块和资源根理解。AppScope 图标服务应用级身份,entry 图标服务 Ability;startWindowIcon 又是启动窗入口。改图时如果只按文件名搜索第一处,很容易只替换其中一层。
当前两个 layered_image.json 内容相同,均把 background 和 foreground 指向同目录资源:
{
"layered-image": {
"background": "$media:background",
"foreground": "$media:foreground"
}
}
结构一致只说明映射相同,不代表两套文件会自动同步。它们是两份独立资源,后续任何一边单独变化,都可能造成展示分叉。
二、当前六个图片角色实际共用同一份字节
只读统计得到以下事实:
| 相对路径 | 字节数 | 尺寸 | 内部格式 | SHA-256 |
|---|---|---|---|---|
| AppScope/…/background.png | 112950 | 1024×1024 | JPEG | 2AE9F16D…F08AE |
| AppScope/…/foreground.png | 112950 | 1024×1024 | JPEG | 2AE9F16D…F08AE |
| entry/…/background.png | 112950 | 1024×1024 | JPEG | 2AE9F16D…F08AE |
| entry/…/foreground.png | 112950 | 1024×1024 | JPEG | 2AE9F16D…F08AE |
| entry/…/startIcon.png | 112950 | 1024×1024 | JPEG | 2AE9F16D…F08AE |
| libraryhsp/…/splash_screen.png | 112950 | 1024×1024 | JPEG | 2AE9F16D…F08AE |
完整摘要为:
2AE9F16D2B4DEA04BCFD86D86FEB7396EAE65EBFD522C1F6BC16168FE42F08AE
两份 layered_image.json 也完全一致,均为 109 字节,SHA-256 是 3D18DCD7ED9E7D3D2C54C20CB80A44EB4877C661131C68A46E20B6E19D0BB476。
“六个文件摘要一致”意味着它们不是看起来相似,而是逐字节相同。当前工程实际上用同一张方形图承担 layered background、layered foreground、启动窗图标和 HSP 全屏闪屏。它可能是阶段性占位选择,但不能据此认为这些角色的裁切、安全区、透明度和构图要求相同。
三、扩展名是 PNG,文件签名却是 JPEG/JFIF
六个 .png 文件的前八字节都是:
FF D8 FF E0 00 10 4A 46
FF D8 FF 是 JPEG 起始标记,后面的 4A 46 对应 JFIF 标识开头。真正 PNG 文件的标准前八字节应为:
89 50 4E 47 0D 0A 1A 0A
使用 Pillow 读取时,六个文件均报告 1024×1024、RGB、format=JPEG。也就是说,当前文件名后缀和内部编码不一致。简单把 .jpg 重命名为 .png 不会转换编码,只会制造这种错配。
为什么要在发布前修正?不同资源编译器、图片优化器、上传平台和设计交付工具对“按后缀判断”还是“按文件头判断”可能不同。某条链路能容忍,不代表另一条也能容忍。JPEG 没有 alpha 通道,还会影响 layered foreground 对透明前景的预期。本文没有运行资源编译,因此不宣称当前一定报错;能确认的是格式真实性已经不满足清晰资产契约。
四、layered background 与 foreground 应分别承担角色
分层图标不是把同一张完整海报放两遍。通常 background 提供底色、纹理或稳定轮廓,foreground 提供主体并保留透明区域,让系统在不同图标蒙版、缩放与动效下组合。当前两层字节完全相同,前景还是 RGB JPEG,没有透明通道,分层意义基本无法从资产层体现。
本文建议在设计交付清单里明确:
layeredIcon:
canvas: 1024x1024
background:
path: AppScope/resources/base/media/background.png
format: PNG
alphaRequired: false
role: stable-background
foreground:
path: AppScope/resources/base/media/foreground.png
format: PNG
alphaRequired: true
role: transparent-subject
safeArea:
reviewedForLauncherMasks: true
sourceDesignVersion: icon-v3
entry 若保留独立 layered_image,也应有自己的两条记录。若产品希望 AppScope 与 entry 始终相同,可以让生成脚本从一个受控设计源输出两套目标文件,并在清单中要求摘要相等;如果它们承担不同入口,则应允许摘要不同,但要求分别经过场景复核。关键是把“相等还是不同”变成有意决策。
五、startIcon 与 splash_screen 不能靠复制应用图标代替
entry 的 startWindowIcon 指向 startIcon;HSP 页面覆盖层使用 splash_screen。两者都与启动有关,但发生阶段不同:startIcon 由系统启动窗读取,splash_screen 是应用页面已经创建后绘制的全屏 Image。前者适合简洁图形和系统裁切,后者可以承载完整品牌画面与背景关系。
当前 startIcon 与 splash_screen 不仅视觉相似,字节完全相同。HSP 使用 ImageFit.Contain 显示 splash,周围背景为 #0A0A12;如果图片本身是方形完整图,宽高比不同的窗口会出现留白区域。系统启动窗也可能对图标做自己的布局。两处共用同一资产无法证明首帧连续。
建议把角色拆开命名并记录:
{
"startWindowIcon": {
"resource": "entry.media.startIcon",
"purpose": "system-start-window-symbol",
"expectedFormat": "PNG",
"alphaPolicy": "approved-by-design"
},
"pageSplash": {
"resource": "libraryhsp.media.splash_screen",
"purpose": "full-screen-page-overlay",
"expectedAspect": "per-device-art-direction",
"backgroundToken": "start_window_background"
}
}
这是一份建议清单,不代表必须更改现有资源名。先保留稳定资源键,再替换内容和补充元数据,通常比同时改键名、路径和图片更容易回归。
六、资源清单要同时核对引用、格式、尺寸和摘要
只比较文件是否存在会漏掉四种问题:后缀与格式不符;同名不同内容;不同角色意外同内容;引用指向错误模块。建议脚本输出固定字段,不输出图片本身。
from dataclasses import dataclass
from hashlib import sha256
from pathlib import Path
PNG_MAGIC = bytes.fromhex("89 50 4E 47 0D 0A 1A 0A")
JPEG_PREFIX = bytes.fromhex("FF D8 FF")
@dataclass(frozen=True)
class AssetFact:
path: str
byte_size: int
encoded_format: str
sha256_hex: str
def read_asset_fact(path: Path) -> AssetFact:
data = path.read_bytes()
if data.startswith(PNG_MAGIC):
encoded = "PNG"
elif data.startswith(JPEG_PREFIX):
encoded = "JPEG"
else:
encoded = "UNKNOWN"
return AssetFact(
path=path.as_posix(),
byte_size=len(data),
encoded_format=encoded,
sha256_hex=sha256(data).hexdigest()
)
尺寸和色彩模式可由图片库继续读取。解析失败要作为硬错误,不要退化为“文件存在即通过”。对 layered foreground 还要核对 alpha;对 splash 要核对目标宽高比和 objectFit;对启动图标要核对安全区。
七、用角色约束发现“意外相同”和“意外不同”
资产摘要相同有两种含义。若 AppScope 与 entry 的应用图标明确要求一致,相同是期望,应写入 sameAs 关系;若 foreground 与 background 设计上必须分层,相同则应阻断;若 startIcon 和 splash 面向不同场景,相同至少要触发人工复核。
type EqualityPolicy =
| 'MUST_EQUAL'
| 'MUST_DIFFER'
| 'REVIEW_IF_EQUAL';
interface AssetRelation {
leftRole: string;
rightRole: string;
policy: EqualityPolicy;
reason: string;
}
const relations: AssetRelation[] = [
{
leftRole: 'appScope.layered',
rightRole: 'entry.layered',
policy: 'MUST_EQUAL',
reason: 'one approved launcher identity'
},
{
leftRole: 'layered.background',
rightRole: 'layered.foreground',
policy: 'MUST_DIFFER',
reason: 'foreground needs independent transparent subject'
},
{
leftRole: 'entry.startIcon',
rightRole: 'hsp.splash',
policy: 'REVIEW_IF_EQUAL',
reason: 'system symbol and full-page image have different framing'
}
];
策略应来自设计决定,而不是脚本自己发明。脚本负责发现事实和执行已有规则;如果 sameAs 关系改变,变更记录要说明原因并附上新预览。
八、真正转换格式要写到新文件,再做原子替换
修复格式不能只改后缀。建议从原始设计源重新导出 PNG;若只能迁移现有 JPEG,可先解码再编码到临时文件,确认格式、尺寸和摘要后再替换。下面是转换流程示意:
from pathlib import Path
from PIL import Image
source = Path("incoming/icon-source.jpg")
staged = Path("staging/foreground.png")
with Image.open(source) as image:
rgba = image.convert("RGBA")
rgba.save(staged, format="PNG", optimize=True)
with Image.open(staged) as verified:
assert verified.format == "PNG"
assert verified.size == (1024, 1024)
assert verified.mode == "RGBA"
#通过清单与人工预览后,再替换目标资源。
把 RGB JPEG 转成 RGBA PNG 并不会自动创造真正透明背景;alpha 仍会全部不透明。foreground 的透明区域应从设计源正确导出,不能靠格式转换伪造。转换代码只解决编码真实性,视觉语义仍由设计交付负责。
九、验证矩阵要覆盖每一个真实展示入口
| 场景 | 资源入口 | 核对内容 | 证据 |
|---|---|---|---|
| 桌面图标 | AppScope layered_image | 蒙版、安全区、前后景 | 启动器截图与资产摘要 |
| 系统设置应用列表 | AppScope icon/label | 名称、图标一致 | 系统页记录 |
| Ability 展示 | entry layered_image | 与产品身份一致 | 任务中心/入口记录 |
| 系统冷启动窗 | entry startIcon + color | 图标尺寸与底色 | 冷启动录屏逐帧 |
| HSP 页面闪屏 | splash_screen | 比例、Contain 留白、文字层 | 页面录屏逐帧 |
| 深色模式 | dark color + 图像 | 边缘是否突兀 | 深色冷启动 |
| phone | 多个入口 | 小屏裁切 | 设备型号与结果 |
| tablet | 多个入口 | 横竖屏与宽屏留白 | 两方向记录 |
| 2in1 | 启动窗与任务中心 | 自由窗口缩放 | 窗口尺寸与结果 |
| 升级安装 | 新旧资源 | 缓存与版本关系 | 升级前后对照 |
图片预览还应直接打开最终资源,而不是只看设计稿。必要时把资源编译后的实际展示与源文件摘要绑定,避免测试看到的还是旧包。
十、故障排查和证据边界
| 现象 | 优先核对 | 当前工程的相关风险 | 处理方向 |
|---|---|---|---|
| 改图后桌面变了,Ability 没变 | AppScope 与 entry 两套目录 | 同名资源独立存在 | 按所有权清单同步 |
| 冷启动仍是旧图 | startWindowIcon | startIcon 是第三个入口 | 单独替换并重建 |
| 页面闪屏还是旧素材 | HSP splash_screen | 资源属于 libraryhsp | 核对 HSP 产物和页面引用 |
| 前景没有透明效果 | 文件模式与 alpha | 当前内部为 RGB JPEG | 从设计源导出真实 RGBA PNG |
| 工具提示格式异常 | 文件头与扩展名 | .png 内部是 JPEG/JFIF | 真正重新编码,不重命名 |
| 六个位置总是一起变 | 摘要与生成源 | 当前六文件完全相同 | 明确角色后拆分资产 |
| 预览正确,设备仍旧 | 包版本和安装缓存 | 交付包可能不是新构建 | 绑定 versionCode 与制品摘要 |
| 深色启动出现黑色边 | base/dark 背景与图片边缘 | 两套底色并不完全相同 | 联合核对第 12 篇链路 |
当前源码已存在: AppScope 与 entry 各有 layered_image.json、background.png、foreground.png;EntryAbility 还引用 startIcon;HSP 页面引用 splash_screen。两份描述文件均为 109 字节且摘要相同。六个光栅文件均为 112950 字节、1024×1024、RGB,SHA-256 完全相同;扩展名是 .png,文件头和 Pillow 读取结果却表明内部编码为 JPEG/JFIF。
本文建议: 建立带资源所有者、展示角色、真实格式、尺寸、alpha、摘要、相等策略和设计版本的清单;从设计源导出真实 PNG;按 AppScope、Ability、系统启动窗和 HSP 闪屏逐入口替换与回归。示例没有修改项目资源。
尚未证明: 本文未执行 hvigorw,未确认当前资源编译器是否接受后缀错配,未生成新包,未清理设备缓存,也未在 phone/tablet/2in1 查看实际图标。字节事实能证明资源当前相同且格式错配,不能证明设备已经出现哪一种视觉故障。最终结论要由新资源、构建产物和多入口设备记录共同支撑。
更多推荐

所有评论(0)