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/7/24 5:07:55 阅读更多 →
孩子沉迷手机停不下来?这套“数字化沟通法”让家长从对立变同盟

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

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

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

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

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

2026/7/23 13:11:13 阅读更多 →

最新新闻

如何一键找回QQ空间所有消失的青春记忆?GetQzonehistory完整备份指南

如何一键找回QQ空间所有消失的青春记忆?GetQzonehistory完整备份指南

如何一键找回QQ空间所有消失的青春记忆?GetQzonehistory完整备份指南 【免费下载链接】GetQzonehistory 获取QQ空间发布的历史说说 项目地址: https://gitcode.com/GitHub_Trending/ge/GetQzonehistory 你是否曾经想要回顾自己多年前在QQ空间发布的说说&…

2026/7/24 17:38:24 阅读更多 →
Flutter---GPS定位(2)

Flutter---GPS定位(2)

功能:跳转主流地图APP实现步骤1.引入外部库map_launcher: ^4.5.0 #检测手机是否安装了主流地图 APP2.增加导航弹窗void showBottomSheetDialog(BuildContext context){showModalBottomSheet(context: context,isScrollControlled: true,backgroundColor: Colors.whi…

2026/7/24 17:38:24 阅读更多 →
没有开放集成与低代码,协同中台只是空谈

没有开放集成与低代码,协同中台只是空谈

当 CIO 查看企业 IM 后台时,常会发现一个尴尬的事实:每天超过 90% 的业务消息都在讨论订单、审批、客户变更,但这些对话永远停留在聊天窗口里,无法直接驱动 ERP 审批流,也不能自动同步到 CRM 客户时间线。另一边&#…

2026/7/24 17:38:24 阅读更多 →
一些感受与总结

一些感受与总结

prd对齐,那些情况要考虑,那些要做哪些不做trd评审,刚好为ai coding材料对齐,遇到问题多问,多和产品、研发和测试同学联系(终于知道以前为什么“明明是”这样却要拉一个会,明确哪些做哪些不做设计…

2026/7/24 17:38:24 阅读更多 →
Unity热重载实战:5分钟配置,告别编译等待,实时调试代码

Unity热重载实战:5分钟配置,告别编译等待,实时调试代码

1. 项目概述:为什么我们需要热重载?如果你是一名Unity开发者,无论是刚入门的新手,还是摸爬滚打多年的老手,下面这个场景你一定不陌生:为了测试一个简单的数值调整,比如把跳跃高度从5改成6&#…

2026/7/24 17:38:23 阅读更多 →
【实战】紧跟 600 亿“再贷款回购”红利:用 Python + QuantDash 筛选央企核心增持股

【实战】紧跟 600 亿“再贷款回购”红利:用 Python + QuantDash 筛选央企核心增持股

导言 / TL;DR 2026 年 7 月中旬,金融监管部门创设了“股票回购增持专项再贷款”政策,多只具有央企背景的核心资产纷纷宣布回购与增持计划[5]。作为量化交易者,如何利用 Python 和 QuantDash 统一接口,快速自动化筛选出“低估值 …

2026/7/24 17:37:23 阅读更多 →

日新闻

用Highcharts 创建可拖拽三维散点立方体3D图表

用Highcharts 创建可拖拽三维散点立方体3D图表

该案例基于Highcharts scatter3d 三维散点图实现空间立方体散点可视化,核心特色:三维 X/Y/Z 三轴空间,所有散点分布在 0~10 立方体空间内;散点使用径向渐变实现立体 3D 圆球质感;支持鼠标 / 触屏拖拽画布,…

2026/7/24 0:00:29 阅读更多 →
AppCertDlls:进程创建路径上的 DLL 入口

AppCertDlls:进程创建路径上的 DLL 入口

AppCertDlls:进程创建路径上的 DLL 入口 AppCertDlls 位于 HKLM\System\CurrentControlSet\Control\Session Manager\AppCertDlls。本文的程序功能是只读列出这个键在 64 位和 32 位注册表视图中的全部值,并显示每条值的来源、名称、类型和可安全显示的数…

2026/7/24 0:00:29 阅读更多 →
我的编程之路:第一篇博客

我的编程之路:第一篇博客

大家好,我是一名编程初学者,同时这也是我编程学习之路上的第一篇博客。在这里,我想要向大家介绍我的一些想法和规划。a.自我介绍我是一个刚刚接触编程的新手,目前在学习c语言,我对编程世界充满了强烈的好奇。当然&…

2026/7/24 0:00:29 阅读更多 →

周新闻

Go语言静态资源打包方案对比与实践指南

Go语言静态资源打包方案对比与实践指南

1. 项目背景与核心需求在Go语言开发中,我们经常需要处理静态资源文件的打包问题。无论是Web应用的模板文件、前端资源,还是配置文件、证书等,都需要随程序一起分发。传统做法是将这些文件与编译后的二进制文件放在同一目录下,但这…

2026/7/24 3:59:20 阅读更多 →
Go语言实现高性能LDAP认证服务的架构与实践

Go语言实现高性能LDAP认证服务的架构与实践

1. 项目背景与核心价值LDAP(轻量级目录访问协议)作为企业级身份认证的黄金标准,已经服务了超过80%的财富500强公司。我在金融科技领域实施统一认证体系时,发现传统Java方案存在启动慢、内存占用高等痛点。而Go语言凭借其协程并发模…

2026/7/24 1:23:39 阅读更多 →
【AI面试官实战指南】:用ChatGPT模拟10类高频技术岗面试,3天提升应答精准度92%

【AI面试官实战指南】:用ChatGPT模拟10类高频技术岗面试,3天提升应答精准度92%

更多请点击: https://intelliparadigm.com 第一章:AI面试官实战指南的核心价值与适用场景 AI面试官并非替代人类HR的“黑箱工具”,而是以可解释、可审计、可迭代的方式,赋能招聘全链路的关键基础设施。其核心价值在于将主观经验沉…

2026/7/23 17:49:47 阅读更多 →

月新闻