茶器艺科智造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.png1129501024×1024JPEG2AE9F16D…F08AE
AppScope/…/foreground.png1129501024×1024JPEG2AE9F16D…F08AE
entry/…/background.png1129501024×1024JPEG2AE9F16D…F08AE
entry/…/foreground.png1129501024×1024JPEG2AE9F16D…F08AE
entry/…/startIcon.png1129501024×1024JPEG2AE9F16D…F08AE
libraryhsp/…/splash_screen.png1129501024×1024JPEG2AE9F16D…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 两套目录同名资源独立存在按所有权清单同步
冷启动仍是旧图startWindowIconstartIcon 是第三个入口单独替换并重建
页面闪屏还是旧素材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 查看实际图标。字节事实能证明资源当前相同且格式错配,不能证明设备已经出现哪一种视觉故障。最终结论要由新资源、构建产物和多入口设备记录共同支撑。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐