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

上图是本文原创生成的概念插画,不是应用截图,也不是用户相册内容。媒体策略的重点不是让卡片“看起来有图”,而是在真实性、可理解性、失败恢复和隐私之间建立可审计的顺序。
一、失物招领里的图片不是普通装饰
校园失物招领页面通常会同时出现三种现实情况:
- 用户已经选择了一张真实物品照片;
- 演示环境需要帮助评委快速理解类别,但不能伪装成真实上传;
- 记录没有照片,或者 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 : '物品'}品类示意图`)
}
这段代码当前只有两条生产路径:
imageUris非空时展示用户选择的第一张照片;- 没有照片时展示
ItemCategoryVisual品类图标。
仓库中的旧演示 JPG 已被移除,所以不能在文章中写成“当前 App 会自动显示演示图”。这也是工程文章必须以当前代码为准,而不是照搬早期规划的原因。
三、三层策略是设计模型,不是强制上线三层
完整的三层模型可以写成:
用户沙箱照片
-> 受控演示图(仅演示构建,必须标注“示意图”)
-> 品类图标兜底
但生产环境的策略可以主动折叠为:
用户沙箱照片
-> 品类图标兜底
折叠不是能力缺失,而是真实性约束的结果。只有当项目明确存在演示构建、资源来源可追踪、界面显著标注“示意图”、测试能证明正式构建不会携带该资源时,第二层才有进入工程的合理性。
四、第一层:用户照片必须拥有最高优先级
用户已经选择照片时,任何内置图片都不应该覆盖它。当前组件通过 imageUris[0] 获取首图,并使用 ImageFit.Cover 填满卡片区域。
这里有三个边界需要明确:
imageUris是用户沙箱或媒体选择结果,不应被当成永久公网 URL;- 列表只展示首图,不等于详情页只能保留一张;
- 公开文章和宣传材料不能直接复用真实用户相册缩略图。
也就是说,数据层可以保存多张 URI,列表媒体组件只承担稳定首图,不承担相册浏览、权限申请或长期文件迁移。
五、第二层:演示图必须被隔离和标注
演示图常见于比赛 Demo、空库演示和销售演示,但它不能偷偷混入正式数据。一个安全的演示层至少需要四道门:
- 仅在明确的 Demo 配置或演示数据集中启用;
- 资源名称、数据记录和 UI 都带有“示意图”语义;
- 不能覆盖用户已上传照片;
- 正式构建或上架资料中能够证明它未被当成用户内容。
寻迹校园当前选择直接移除这一层,避免评委或用户把生成图片误认为真实失物照片。这比在角落放一个很小的标签更容易审计。
六、第三层:品类图标不是失败,而是安全默认值
没有照片时显示“箱包”“证件”“电子设备”等品类图标,能保留三个价值:
- 卡片仍有稳定的视觉锚点;
- 不引入虚假的物品外观;
- 辅助技术仍可获得类别语义。
ItemCategoryVisual 还接收 reportType,因此丢失与拾得记录可以沿用各自的状态色,而不是所有空图都变成同一个灰色方块。
七、ReportMedia 为什么要做成统一组件
当前组件被首页记录卡片、详情、匹配候选和举报页面共同调用。统一组件带来的不是少写几行代码,而是同一条媒体契约:
| 页面 | 组件目标 | 不能自行决定的事 |
|---|---|---|
| 首页 | 快速识别记录 | 不能自行加载演示资源 |
| 详情 | 保持来源一致 | 不能把缺图解释为数据错误 |
| 匹配 | 对比候选 | 不能因为有图就提高归属置信度 |
| 举报 | 确认关联记录 | 不能暴露私密核验信息 |
如果某个页面需要更大的尺寸,只传入 mediaWidth、mediaHeight 和 fallbackSize;来源优先级仍由组件统一控制。
八、当前仍缺少显式的图片加载失败回退
imageUris.length > 0 只能证明存在 URI 字符串,不能证明文件仍可读。用户清理沙箱、系统回收临时授权或文件损坏后,Image 可能加载失败。
当前 ReportMedia 没有维护 onError 状态,因此“有 URI 但加载失败后自动切到品类图标”仍是待补能力。文章不能把预期设计写成已实现。
可演进的状态应该是:
hasUri && !loadFailed -> Image
otherwise -> ItemCategoryVisual
Image.onError -> loadFailed = true
report changed -> loadFailed = false
这里的关键是“记录变化时重置失败状态”。如果组件复用后切换到另一条有效记录,却仍保留上一条的 loadFailed,新照片也会被错误隐藏。

上图将生产与演示边界拆开:生产只接受真实用户照片,加载失败直接进入品类兜底;演示图片是可选的开发路径,不能成为生产默认路径。
九、不要让图片参与“归属判断”
寻迹校园的匹配结果明确提示“信息相似分仅用于排序,不代表物品归属”。图片同样只能帮助用户识别,不能因为“看起来很像”就跳过私密特征核验。
正确的边界是:
- 公开照片帮助缩小候选;
- 私密特征由认领流程核验;
- 最终归还需要双方确认;
- 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:业务页面四类状态如何避免布局跳动》。
更多推荐


所有评论(0)