【寻迹校园 HarmonyOS NEXT 实战 41】用户照片、演示图与图标兜底:ReportMedia 的三层媒体策略

本章导读:这是“寻迹校园 HarmonyOS NEXT 实战”系列第 41 篇。本文从 ReportMedia.ets 的正式代码出发,解释失物招领场景为什么需要“用户照片、受控演示图、品类图标”三层媒体策略,同时说明寻迹校园当前正式实现已经移除演示图片,只保留“用户照片优先、品类图标兜底”两层。三层是完整设计模型,不代表三层都应该进入生产包。

ReportMedia 三层媒体策略原创封面图

上图是本文原创生成的概念插画,不是应用截图,也不是用户相册内容。媒体策略的重点不是让卡片“看起来有图”,而是在真实性、可理解性、失败恢复和隐私之间建立可审计的顺序。

一、失物招领里的图片不是普通装饰

校园失物招领页面通常会同时出现三种现实情况:

  • 用户已经选择了一张真实物品照片;
  • 演示环境需要帮助评委快速理解类别,但不能伪装成真实上传;
  • 记录没有照片,或者 URI 已经失效,页面仍要保持可识别、可点击、可朗读。

如果页面分别用 if、硬编码资源和临时占位处理,很快会出现首页是一张图、详情页是另一张图、匹配页没有图、举报弹层又使用默认头像的割裂体验。更严重的是,演示素材可能在正式包中被误认为真实用户内容。

因此,媒体显示不能只问“有没有图片”,还要问四件事:数据来自哪里、是否允许在当前构建中出现、失败后落到哪里、辅助技术应该播报什么。

二、先看当前正式代码的真实状态

当前 entry/src/main/ets/components/ReportMedia.ets 的判断很克制:

if (this.report.imageUris.length > 0) {
  Image(this.report.imageUris[0])
    .width(this.mediaWidth)
    .height(this.mediaHeight)
    .objectFit(ImageFit.Cover)
    .accessibilityText('用户上传的物品照片')
} else {
  ItemCategoryVisual({
    category: this.report.category,
    reportType: this.report.reportType,
    size: this.fallbackSize
  })
    .width(this.mediaWidth)
    .height(this.mediaHeight)
    .accessibilityText(`${this.report.category.length > 0 ? this.report.category : '物品'}品类示意图`)
}

这段代码当前只有两条生产路径:

  1. imageUris 非空时展示用户选择的第一张照片;
  2. 没有照片时展示 ItemCategoryVisual 品类图标。

仓库中的旧演示 JPG 已被移除,所以不能在文章中写成“当前 App 会自动显示演示图”。这也是工程文章必须以当前代码为准,而不是照搬早期规划的原因。

三、三层策略是设计模型,不是强制上线三层

完整的三层模型可以写成:

用户沙箱照片
  -> 受控演示图(仅演示构建,必须标注“示意图”)
  -> 品类图标兜底

但生产环境的策略可以主动折叠为:

用户沙箱照片
  -> 品类图标兜底

折叠不是能力缺失,而是真实性约束的结果。只有当项目明确存在演示构建、资源来源可追踪、界面显著标注“示意图”、测试能证明正式构建不会携带该资源时,第二层才有进入工程的合理性。

四、第一层:用户照片必须拥有最高优先级

用户已经选择照片时,任何内置图片都不应该覆盖它。当前组件通过 imageUris[0] 获取首图,并使用 ImageFit.Cover 填满卡片区域。

这里有三个边界需要明确:

  • imageUris 是用户沙箱或媒体选择结果,不应被当成永久公网 URL;
  • 列表只展示首图,不等于详情页只能保留一张;
  • 公开文章和宣传材料不能直接复用真实用户相册缩略图。

也就是说,数据层可以保存多张 URI,列表媒体组件只承担稳定首图,不承担相册浏览、权限申请或长期文件迁移。

五、第二层:演示图必须被隔离和标注

演示图常见于比赛 Demo、空库演示和销售演示,但它不能偷偷混入正式数据。一个安全的演示层至少需要四道门:

  • 仅在明确的 Demo 配置或演示数据集中启用;
  • 资源名称、数据记录和 UI 都带有“示意图”语义;
  • 不能覆盖用户已上传照片;
  • 正式构建或上架资料中能够证明它未被当成用户内容。

寻迹校园当前选择直接移除这一层,避免评委或用户把生成图片误认为真实失物照片。这比在角落放一个很小的标签更容易审计。

六、第三层:品类图标不是失败,而是安全默认值

没有照片时显示“箱包”“证件”“电子设备”等品类图标,能保留三个价值:

  • 卡片仍有稳定的视觉锚点;
  • 不引入虚假的物品外观;
  • 辅助技术仍可获得类别语义。

ItemCategoryVisual 还接收 reportType,因此丢失与拾得记录可以沿用各自的状态色,而不是所有空图都变成同一个灰色方块。

七、ReportMedia 为什么要做成统一组件

当前组件被首页记录卡片、详情、匹配候选和举报页面共同调用。统一组件带来的不是少写几行代码,而是同一条媒体契约:

页面 组件目标 不能自行决定的事
首页 快速识别记录 不能自行加载演示资源
详情 保持来源一致 不能把缺图解释为数据错误
匹配 对比候选 不能因为有图就提高归属置信度
举报 确认关联记录 不能暴露私密核验信息

如果某个页面需要更大的尺寸,只传入 mediaWidthmediaHeightfallbackSize;来源优先级仍由组件统一控制。

八、当前仍缺少显式的图片加载失败回退

imageUris.length > 0 只能证明存在 URI 字符串,不能证明文件仍可读。用户清理沙箱、系统回收临时授权或文件损坏后,Image 可能加载失败。

当前 ReportMedia 没有维护 onError 状态,因此“有 URI 但加载失败后自动切到品类图标”仍是待补能力。文章不能把预期设计写成已实现。

可演进的状态应该是:

hasUri && !loadFailed -> Image
otherwise            -> ItemCategoryVisual
Image.onError         -> loadFailed = true
report changed        -> loadFailed = false

这里的关键是“记录变化时重置失败状态”。如果组件复用后切换到另一条有效记录,却仍保留上一条的 loadFailed,新照片也会被错误隐藏。

ReportMedia 媒体状态机原创结构图

上图将生产与演示边界拆开:生产只接受真实用户照片,加载失败直接进入品类兜底;演示图片是可选的开发路径,不能成为生产默认路径。

九、不要让图片参与“归属判断”

寻迹校园的匹配结果明确提示“信息相似分仅用于排序,不代表物品归属”。图片同样只能帮助用户识别,不能因为“看起来很像”就跳过私密特征核验。

正确的边界是:

  • 公开照片帮助缩小候选;
  • 私密特征由认领流程核验;
  • 最终归还需要双方确认;
  • AI 或图片相似度不能替代所有权判断。

媒体组件属于展示层,不应把识别、匹配打分或认领业务塞进 build()

十、无障碍文本必须说明内容来源

当前用户照片播报“用户上传的物品照片”,品类兜底播报“某某品类示意图”。这比统一播报“图片”更有用,因为读屏用户能区分真实照片和类别插画。

如果未来恢复演示层,必须使用不同文案,例如“演示数据示意图,不是用户上传照片”。仅靠视觉角标不能覆盖读屏场景。

还要避免把文件名、沙箱 URI 或内部资源 key 作为无障碍名称,它们对用户没有意义,也可能暴露实现细节。

十一、尺寸变化不应改变语义顺序

列表可以使用 64vp,详情或候选可以使用 72vp,但组件的来源优先级不能因为断点改变。Phone、md、lg、xl 只调整尺寸和容器编排,不应出现“大屏显示演示图、小屏显示品类图标”的分叉。

这也是共享组件优于页面内条件分支的地方:响应式只改变布局,不改变内容真实性。

十二、缓存与生命周期要放在正确层

媒体组件不应自己复制文件、申请相册权限或修改记录。合理的责任分层是:

ArkUI 页面 / ReportMedia:展示当前 URI 或兜底图标
PhotoPickerService:用户明确触发选择
ReportService:保存记录与图片 URI
Repository / 文件层:持久化与迁移

如果需要把临时 URI 复制到应用沙箱,应在保存流程完成,并返回稳定结果;不能等列表渲染时才偷偷复制。

十三、加载态不要用假照片占位

读取记录期间,可以使用固定尺寸的骨架、LoadingProgress 或品类占位,但不要临时塞入一张看似真实的书包照片。否则加载完成后图片突然变化,用户会误以为记录内容被替换。

占位应保持相同宽高和圆角,减少布局跳动;内容真实性则通过明确的中性视觉表达。

十四、隐私检查不能只看正文

公开技术文章至少要检查:

  • 图片中是否出现真实相册缩略图;
  • 沙箱路径是否包含账号或设备信息;
  • EXIF 是否保留位置、时间或设备型号;
  • 认领私密答案是否出现在卡片;
  • 封面是否被误认为产品真实截图。

本文两张图均为原创技术插画,并在正文明确标注不是项目截图。

十五、建议的测试矩阵

场景 预期
有一张有效用户照片 显示首图,播报用户上传照片
无照片 显示品类图标,播报品类示意图
URI 存在但文件失效 当前实现需补 onError 回退
类别为空 使用“物品”通用语义
记录在列表复用 不继承上一条记录的失败状态
Demo 构建启用演示资源 必须显著标注且不覆盖用户照片
Production 构建 不读取演示资源

十六、本章证据边界

本章能证明:当前代码统一使用 ReportMedia,用户照片优先,无照片时使用品类图标,并提供区分来源的无障碍文本。

本章不能证明:图片加载失败已经自动回退、演示层当前仍在生产代码、所有沙箱 URI 都能跨升级长期有效、读屏全流程已经验收。

十七、小结

三层媒体策略真正解决的是媒体真实性和失败恢复,而不是“每张卡片都要有一张好看的照片”。寻迹校园当前正式实现主动删除演示图层,只保留用户照片与品类图标,是对上架真实性要求的收敛。

下一步若补 Image.onError,应继续维持统一组件、记录切换重置状态、无障碍来源说明和生产/演示构建隔离,而不是把新的条件分支散落到每个页面。

下一篇:《【寻迹校园 HarmonyOS NEXT 实战 42】Loading、Empty、Error、Disabled:业务页面四类状态如何避免布局跳动》。

Logo

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

更多推荐