HarmonyOS技术精讲-Camera Kit(相机服务)第18篇:媒体库集成与管理
HarmonyOS技术精讲-Camera Kit相机服务第18篇媒体库集成与管理拍完的照片究竟藏哪了HarmonyOS NEXT 开发里Camera Kit 本身只管拍照和录像不负责文件管理。这意味着你拍完一张照片如果不去主动保存到媒体库它就是个临时文件系统相册里根本看不到。很多人第一次对接这个链路时会习惯性地调用save()方法保存到应用沙箱然后发现系统相册里找不到用户会直接干懵。这个问题在应用市场反馈里非常常见。这里的关键在于Camera Kit 只管拍摄MediaLibrary Kit 才管文件的持久化存储和系统索引。两者是上下游关系不是同层关系。本文通过一个带管理功能的相册页面完整演示从拍摄到存储、从查询到删除的全链路。环境说明DevEco Studio 版本DevEco Studio 6.1.0 及以上 HarmonyOS SDK 版本HarmonyOS 6.1.0(23) 及以上 目标设备手机核心实现整个功能分两部分相册页面展示和管理逻辑。下面按步骤拆解。步骤 1权限申请MediaLibrary Kit 的操作需要ohos.permission.READ_MEDIA和ohos.permission.WRITE_MEDIA权限。在module.json5中声明{module:{requestPermissions:[{name:ohos.permission.CAMERA,reason:用于拍照},{name:ohos.permission.READ_MEDIA,reason:用于读取媒体文件},{name:ohos.permission.WRITE_MEDIA,reason:用于保存媒体文件}]}}注意READ_MEDIA和WRITE_MEDIA是用户授权类权限真机和模拟器上都需要弹窗确认。步骤 2创建媒体文件并保存拍照后调用photoOutput.capture()得到Photo对象。然后通过 MediaLibrary API 创建媒体 Asset 并写入数据。import{photoAccessHelper}fromkit.MediaLibraryKit;import{image}fromkit.ImageKit;import{photoOutput}fromkit.CameraKit;asyncfunctionsavePhoto(photo:photoOutput.Photo):Promisestring{// 获取照片的 Buffer 数据constimageSource:image.ImageSourceimage.createImageSource(photo.main.uri);constpixelMap:image.PixelMapawaitimageSource.createPixelMap();constbuffer:ArrayBufferawaitpixelMap.readPixelsToBuffer();// 创建媒体库实例constcontext:ContextgetContext(this);constmediaLibrary:photoAccessHelper.PhotoAccessHelperphotoAccessHelper.getPhotoAccessHelper(context);// 创建媒体文件指定类型为图片consturi:stringawaitmediaLibrary.createAsset(photoAccessHelper.PhotoType.IMAGE,jpg);constfile:fileIo.FileawaitfileIo.open(uri,fileIo.OpenMode.WRITE_ONLY);awaitfileIo.write(file.fd,buffer);awaitfileIo.close(file);returnuri;}几个关键点createAsset会先在媒体库中创建一个 Asset 记录返回一个 URI。这个 URI 可以理解为系统相册的引用点。写入数据时一定要用WRITE_ONLY模式如果传READ_WRITE会导致写入失败。写入完成必须close否则文件句柄泄露。步骤 3查询相册列表查询已保存的照片用于相册页面展示。这里使用getAssets方法配合FetchOptions进行筛选。import{photoAccessHelper}fromkit.MediaLibraryKit;asyncfunctionqueryPhotos():PromisephotoAccessHelper.PhotoAsset[]{constcontext:ContextgetContext(this);constmediaLibrary:photoAccessHelper.PhotoAccessHelperphotoAccessHelper.getPhotoAccessHelper(context);// 设置查询条件只查询图片类型按日期倒序constfetchOptions:photoAccessHelper.FetchOptions{selections:${photoAccessHelper.PhotoKeys.PHOTO_TYPE} ?,selectionArgs:[photoAccessHelper.PhotoType.IMAGE.toString()],order:date_added DESC// 按添加时间降序};constfetchResult:photoAccessHelper.FetchResultphotoAccessHelper.PhotoAssetawaitmediaLibrary.getAssets(fetchOptions);constassets:photoAccessHelper.PhotoAsset[][];// 遍历结果集for(leti0;ifetchResult.count;i){constasset:photoAccessHelper.PhotoAssetawaitfetchResult.getObjectByIndex(i);assets.push(asset);}returnassets;}这里有一个容易忽略的地方order参数的值必须严格按照PhotoKeys定义的字符串写否则查询会静默失败返回空结果集。步骤 4删除操作删除媒体文件需要先获取 Asset 对象然后调用deleteAssets。import{photoAccessHelper}fromkit.MediaLibraryKit;asyncfunctiondeletePhoto(uri:string):Promisevoid{constcontext:ContextgetContext(this);constmediaLibrary:photoAccessHelper.PhotoAccessHelperphotoAccessHelper.getPhotoAccessHelper(context);// 通过 URI 查询到具体 AssetconstfetchOptions:photoAccessHelper.FetchOptions{selections:${photoAccessHelper.PhotoKeys.URI} ?,selectionArgs:[uri]};constfetchResult:photoAccessHelper.FetchResultphotoAccessHelper.PhotoAssetawaitmediaLibrary.getAssets(fetchOptions);constasset:photoAccessHelper.PhotoAssetawaitfetchResult.getObjectByIndex(0);// 删除支持批量删除这里只删除一个awaitmediaLibrary.deleteAssets([asset]);}deleteAssets接收的是一个数组意味着可以批量删除。但需要注意删除操作会同时删除系统相册中的文件无法通过应用侧恢复。步骤 5完整相册列表页面把上面步骤串起来做一个带管理功能的页面。import{photoAccessHelper}fromkit.MediaLibraryKit;EntryComponentstruct AlbumPage{StatephotoAssets:photoAccessHelper.PhotoAsset[][];aboutToAppear():void{this.loadPhotos();}asyncloadPhotos():Promisevoid{try{constassets:photoAccessHelper.PhotoAsset[]awaitqueryPhotos();this.photoAssetsassets;}catch(error){console.error(查询相册失败: JSON.stringify(error));}}build(){Column(){List({space:10}){ForEach(this.photoAssets,(asset:photoAccessHelper.PhotoAsset){ListItem(){Row(){Image(asset.uri).width(40%).aspectRatio(1).objectFit(ImageFit.Cover).onClick(async(){// 点击查看大图此处可扩展})Column(){Text(asset.title).fontSize(14)Text(asset.dateAdded.toString()).fontSize(12).fontColor(Color.Gray)Button(删除).type(ButtonType.Normal).onClick(async(){awaitdeletePhoto(asset.uri);// 删除后刷新列表awaitthis.loadPhotos();})}.padding({left:10})}.width(100%)}},(asset:photoAccessHelper.PhotoAsset)asset.uri)}.width(100%)}.padding(10)}}为什么这里用ForEach而不是LazyForEach因为相册照片数量通常不会太长几百张以内用ForEach够用。如果业务上有大量图片比如图库应用换成LazyForEach避免一次性渲染所有列表项。常见问题 1删除后相册不刷新现象调用deleteAssets成功后再次查询依然能看到已删除的照片。原因媒体库的文件删除是异步完成的。deleteAssets返回只表示删请求已提交不代表物理删除已完成。此时立即查询结果可能包含尚未清理的缓存。解决方案删除成功后等待 500ms-1s 再重新查询或者监听媒体库的变化事件on(albumChange)但监听回调需要额外管理生命周期容易内存泄漏。推荐做法是删除后延迟刷新asyncfunctiondeletePhotoAndRefresh(uri:string):Promisevoid{awaitdeletePhoto(uri);// 延迟 1 秒以保证删除生效awaitnewPromise(resolvesetTimeout(resolve,1000));awaitloadPhotos();}常见问题 2权限弹窗被拒绝后无法恢复现象用户第一次拒绝权限弹窗后后续再请求不会再弹窗导致媒体库操作全部失败。原因HarmonyOS 对敏感权限的弹窗策略是用户拒绝一次后系统不会重复弹窗需要用户手动到设置中开启。解决方案在权限被拒绝后给用户一个明确的提示引导去设置页手动开启。import{abilityAccessCtrl}fromkit.AbilityKit;asyncfunctionrequestPermission(context:Context):Promiseboolean{constatManager:abilityAccessCtrl.AtManagerabilityAccessCtrl.createAtManager();constresult:numberawaitatManager.requestPermissionsFromUser(context,[ohos.permission.READ_MEDIA,ohos.permission.WRITE_MEDIA]);if(resultabilityAccessCtrl.GrantStatus.PERMISSION_DENIED){// 引导用户去设置页AlertDialog.show({title:权限被拒绝,message:请在系统设置中手动开启媒体库读写权限,confirm:{value:去设置,action:(){// 调用跳转系统设置页面的 API}}});returnfalse;}returntrue;}最佳实践不要在build()中创建 MediaLibrary 实例。photoAccessHelper.getPhotoAccessHelper是一个同步 API放在aboutToAppear或onPageShow中初始化避免重复创建。异步操作记得try/catch。媒体库的读写可能因为存储空间不足、文件损坏等异常而失败。建议对每个关键步骤加异常处理至少日志输出便于排查。状态同步用State响应不要手动赋值。上面示例中this.photoAssets是State修饰的修改后会触发 UI 刷新。如果直接let assets this.photoAssets然后assets.push是不会刷新列表的。FAQQ查询返回的 URI 是content://格式为什么用Image组件能直接显示AHarmonyOS 的Image组件支持content://协议开头的内容 URI会自动解析并加载图片。不需要手动转换为文件路径。Q删除照片后系统相册中的应用图标也会消失吗A不会。删除文件只会删除该媒体文件本身应用在系统相册中的项目依然存在只是无法打开预览。总结MediaLibrary Kit 是 Camera Kit 的必备搭档。看似简单的增删查实际上隐藏着权限状态、查询条件、异步延迟这些坑。实际项目中建议统一封装一个媒体管理服务把查询、删除、刷新逻辑收敛到同一个DataService中避免页面直接操作媒体库实例。

相关新闻

2026权威实测:16款AI智能降重工具测评,这款让导师都夸“原创性强”!

2026权威实测:16款AI智能降重工具测评,这款让导师都夸“原创性强”!

随着AI写作技术的不断进步,越来越多的学术研究者开始借助智能工具提升论文写作效率。然而,2026年各大高校与科研机构对AIGC检测的审查标准愈发严格,论文中若存在明显的AI痕迹,轻则影响成绩,重则面临学术不端的质疑。在…

2026/10/12 15:04:02 阅读更多 →
孩子沉迷手机停不下来?这套“数字化沟通法”让家长从对立变同盟

孩子沉迷手机停不下来?这套“数字化沟通法”让家长从对立变同盟

引言:一个让所有父母头疼的深夜场景 晚上十一点,你推开孩子的房门,果然——台灯关着,但被窝里透出微弱的蓝光。手机屏幕的亮光映着孩子的脸,他戴着耳机,完全没有察觉你的到来。你深吸一口气,是该…

2026/10/12 16:00:37 阅读更多 →
苹果开发者账号注册教程 个人与公司账号的区别和申请流程

苹果开发者账号注册教程 个人与公司账号的区别和申请流程

上架 App Store 的第一步是注册苹果开发者账号。个人和公司两种账号的费用都是 688 元/年,但在申请流程、所需资料和功能权限上有一些区别。选哪种账号取决于你的开发身份和团队结构。 个人账号 个人账号注册速度最快,资料提交后一般 1 天左右就能收到审…

2026/10/12 16:00:37 阅读更多 →

最新新闻

基于JavaWeb的图书馆管理系统:从数据库设计到答辩全流程拆解

基于JavaWeb的图书馆管理系统:从数据库设计到答辩全流程拆解

每年我的邮箱和私信都会被同一个问题塞满:“老师,有没有现成的毕设项目?最好简单点、能跑通、好答辩。”今年那批600套毕设分享出来之后,我翻了一圈,发现图书馆管理系统仍然是出现频率最高的题目之一。说句实话&#x…

2026/10/13 15:59:05 阅读更多 →
VirtualBox VMX源码深度解析:VMCS结构与硬件虚拟化实现

VirtualBox VMX源码深度解析:VMCS结构与硬件虚拟化实现

1. 为什么从VMX切入看VirtualBox源码——不是选最炫的,而是选最“痛”的很多人一看到“VirtualBox源码分析”,第一反应是去翻src/VBox/Devices里那些网卡、显卡模拟器的代码,觉得“看得见摸得着”。我试过,也这么干过——结果两周…

2026/10/13 15:59:05 阅读更多 →
精品水果线上销售网站设计与实现:Spring Boot+Vue全栈电商实战

精品水果线上销售网站设计与实现:Spring Boot+Vue全栈电商实战

1. 内容整体设计与思路拆解做毕业设计选“精品水果线上销售网站”,这个题目乍一看平平无奇,但真正动手做起来,你会发现它其实是把前端展示、后端业务、数据库设计、支付流程、权限控制全串起来的一个完整闭环。我见过太多人选电商类题目最后做…

2026/10/13 15:59:05 阅读更多 →
基于Spring Boot+Vue的精品水果销售网站设计与实现全解析

基于Spring Boot+Vue的精品水果销售网站设计与实现全解析

这段时间正好在帮一位学弟审他的毕业设计,题目就是“精品水果线上销售网站的设计与实现”。说实话,看到这个题目的时候我愣了一下——现在网上商城类的毕设选题太多了,图书、服饰、数码应有尽有,但“精品水果”这个切入点有点意思…

2026/10/13 15:59:05 阅读更多 →
LiteLLM实战:统一大模型API调用与代理网关搭建指南

LiteLLM实战:统一大模型API调用与代理网关搭建指南

如果你和我一样,每天要跟三四个不同的大模型API打交道,可能早就被这一堆SDK和鉴权方式搞得头大了。我一开始是拿LiteLLM当“偷懒工具”用的:它能把OpenAI、Anthropic、Gemini这些五花八门的接口统一成一个Python调用,后来才发现这…

2026/10/13 15:59:05 阅读更多 →
STM32电源闭环控制:ADC触发+双环PID+硬件保护实战

STM32电源闭环控制:ADC触发+双环PID+硬件保护实战

简介:本资源为电子设计大赛(电赛)电源类专项程序代码合集,面向高校电子类专业学生、电赛备赛团队及嵌入式电源系统开发者,聚焦电源管理、闭环控制与硬件协同等核心难点,助力快速构建稳定可靠的电源子系统。…

2026/10/13 15:58:04 阅读更多 →

日新闻

物联网系统集成避坑指南:从选型到验收的关键要点

物联网系统集成避坑指南:从选型到验收的关键要点

做了这么多年物联网系统集成的项目,我最大的感受是,大部分坑不是出在技术前沿,而是出在一些看起来很平常的地方。设备选了便宜的、协议没问清楚、现场没摸透、验收标准写得含糊——这些细枝末节,最后都会变成延期、超支和扯皮的大…

2026/10/13 0:00:57 阅读更多 →
ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查

ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查

如果你玩ESP32有一阵子了,很难不碰到舵机控制这道坎。做智能小车要转方向,做机械臂要抬关节,做云台要偏航俯仰,背后都是同一个东西在干活:舵机。而我见过太多人卡在“代码能编译、舵机就是不听话”这个阶段&#xff0c…

2026/10/13 0:00:57 阅读更多 →
CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析

CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析

1. 为什么CAN总线至今仍是工业现场的“硬通货”?——从汽车底盘到智能温室的真实需求你有没有拆过一辆老款燃油车的中控台?或者调试过某高校实验室里那台跑着Modbus协议却频频丢帧的PLC控制柜?又或者,在某个农业物联网项目里&…

2026/10/13 0:00:57 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/13 2:54:56 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/12 0:16:38 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/13 1:58:08 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 21:05:09 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/13 14:57:39 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 18:11:17 阅读更多 →