OpenHarmony 图片选择 相册相机工具封装 ImagePickerUtil(API23)

发布时间:2026/7/17 0:50:58

OpenHarmony 图片选择  相册相机工具封装 ImagePickerUtil(API23)
摘要原生媒体图库、相机拍照接口逻辑繁琐需要先校验动态权限、区分相册单选 / 多选、相机拍摄、图片压缩、沙盒路径转换、保存到相册、权限拒绝引导弹窗、回调封装。封装统一图片工具整合相册单选、相册多选、相机拍照、图片压缩、本地保存五大能力依赖前文 PermissionUtil 权限工具、DialogUtil 弹窗工具、文件工具统一串联省去每个页面重复写权限判断、媒体拉起逻辑。API23 重构媒体选择器、图片沙盒访问、媒体存储写入权限逻辑修复多选图片路径失效、拍照保存相册失败、高分辨率大图 OOM、权限回调页面销毁报错等问题。关键词OpenHarmony媒体图库ImagePicker相机拍照图片压缩媒体存储动态权限沙盒文件一、引言1.1 原生媒体开发痛点每次打开相册 / 相机都要手动校验读写媒体、相机两套动态权限代码重复媒体选择器无统一回调单选、多选参数写法不一致拍摄原图分辨率过高直接上传容易内存溢出、上传缓慢拍摄图片默认存在应用沙盒需要额外逻辑写入系统相册用户永久拒绝媒体权限无弹窗引导跳转设置页面销毁后媒体选择回调执行操作已销毁页面组件崩溃。1.2 依赖模块etsimport picker from ohos.file.picker import camera from ohos.multimedia.camera import image from ohos.multimedia.image import fileIo from ohos.fileio import mediaLibrary from ohos.multimedia.mediaLibrary import common from ohos.app.ability.common配套依赖工具PermissionUtil、DialogUtil、FileUtilAPI23 媒体核心变更废弃旧版 photoAccessHelper统一使用 file.picker 图库选择器媒体文件路径区分沙盒私有路径、公共媒体路径转换规则标准化新增图片解码压缩 API限制宽高降低内存占用媒体写入相册强制校验 WRITE_MEDIA 动态权限媒体选择弹窗生命周期与页面绑定页面销毁自动关闭选择器。二、完整图片工具封装 utils/image_picker_util.etsetsimport picker from ohos.file.picker import image from ohos.multimedia.image import mediaLibrary from ohos.multimedia.mediaLibrary import fileIo from ohos.fileio import common from ohos.app.ability.common import promptAction from ohos.promptAction import PermissionUtil, { PERMISSION } from ./permission_util import DialogUtil from ./dialog_util // 图片返回结果类型 export interface ImageResult { uri: string path: string pixelMap?: image.PixelMap } // 图片压缩配置 interface CompressOption { width: number height: number quality: number // 0-100 } class ImagePickerUtil { private static instance: ImagePickerUtil private ctx: common.UIAbilityContext | null null static getInstance(): ImagePickerUtil { if (!ImagePickerUtil.instance) { ImagePickerUtil.instance new ImagePickerUtil() } return ImagePickerUtil.instance } setContext(context: common.UIAbilityContext) { this.ctx context PermissionUtil.setContext(context) } // 压缩图片返回PixelMap async compressImage(uri: string, opt: CompressOption): Promiseimage.PixelMap | null { try { const source image.createImageSource(uri) const decodeOpt: image.DecodingOptions { desiredSize: { width: opt.width, height: opt.height } } const pixelMap await source.createPixelMap(decodeOpt) source.release() return pixelMap } catch (err) { promptAction.showToast({ message: 图片压缩失败 }) console.error(compress err, err) return null } } // PixelMap保存到应用沙盒 async savePixelToSandbox(pixelMap: image.PixelMap, fileName: string, quality: number 70): Promisestring | null { if (!this.ctx) return null const outPath this.ctx.filesDir / fileName .jpg const packer image.createImagePacker() const packOpt: image.PackingOption { format: image.ImageFormat.JPEG, quality: quality } const packData await packer.packing(pixelMap, packOpt) const fd await fileIo.open(outPath, fileIo.OpenMode.CREATE | fileIo.OpenMode.WRITE_ONLY) await fileIo.write(fd, packData) fileIo.close(fd) packer.release() return outPath } // 保存图片到系统公共相册 async saveImageToAlbum(filePath: string): Promiseboolean { if (!this.ctx) return false // 校验相册写入权限 const hasWrite PermissionUtil.checkPermission(PERMISSION.WRITE_MEDIA) if (!hasWrite) { promptAction.showToast({ message: 无相册写入权限 }) return false } try { const media mediaLibrary.getMediaLibrary(this.ctx) const fileInfo await media.createAsset(mediaLibrary.MediaType.IMAGE, img_ Date.now() .jpg, mediaLibrary.DirectoryType.DIR_IMAGE) const targetFd await fileInfo.open(mediaLibrary.FileMode.WRITE) const sourceFd await fileIo.open(filePath, fileIo.OpenMode.READ_ONLY) const buf new ArrayBuffer(1024 * 1024) let len: number while ((len await fileIo.read(sourceFd, buf)) 0) { await fileIo.write(targetFd, buf.slice(0, len)) } fileIo.close(sourceFd) fileIo.close(targetFd) await fileInfo.close() await media.release() return true } catch (err) { console.error(save album err, err) return false } } // 打开相册 单选图片 async pickSingleImage(compressOpt?: CompressOption): PromiseImageResult | null { // 校验相册读写权限 let authOk false await new Promisevoid(res { PermissionUtil.reqImage((granted) { authOk granted res() }) }) if (!authOk) return null try { const pickerOpts new picker.PhotoPickerOptions() pickerOpts.MIMEType picker.MIMEType.IMAGE pickerOpts.isMultiSelect false const photoPicker new picker.PhotoPicker(pickerOpts) const resultList await photoPicker.select() if (resultList.length 0) return null const item resultList[0] const res: ImageResult { uri: item.uri, path: item.fileUri } // 压缩处理 if (compressOpt) { res.pixelMap await this.compressImage(item.uri, compressOpt) } return res } catch (err) { promptAction.showToast({ message: 选择图片失败 }) return null } } // 打开相册 多选图片 async pickMultiImage(maxCount: number, compressOpt?: CompressOption): PromiseImageResult[] { let authOk false await new Promisevoid(res { PermissionUtil.reqImage((granted) { authOk granted res() }) }) if (!authOk) return [] try { const pickerOpts new picker.PhotoPickerOptions() pickerOpts.MIMEType picker.MIMEType.IMAGE pickerOpts.isMultiSelect true pickerOpts.maxSelectNumber maxCount const photoPicker new picker.PhotoPicker(pickerOpts) const resultList await photoPicker.select() const outList: ImageResult[] [] for (const item of resultList) { const obj: ImageResult { uri: item.uri, path: item.fileUri } if (compressOpt) { obj.pixelMap await this.compressImage(item.uri, compressOpt) } outList.push(obj) } return outList } catch (err) { promptAction.showToast({ message: 多选图片失败 }) return [] } } // 唤起相机拍照简化封装 async takePhoto(compressOpt?: CompressOption): PromiseImageResult | null { let cameraAuth false await new Promisevoid(res { PermissionUtil.reqCamera((granted) { cameraAuth granted res() }) }) if (!cameraAuth) return null // 简易相机拍摄流程实际项目可封装独立相机页面 DialogUtil.alert({ content: 当前为工具模拟相机真实项目需独立相机页面承载camera组件 }) return null } } export default ImagePickerUtil.getInstance()三、页面实战调用示例3.1 头像上传页面相册单选 压缩etsimport ImagePickerUtil, { ImageResult } from ../utils/image_picker_util import HttpUtil from ../utils/http_util Entry Component struct AvatarPage { State avatarUri: ResourceStr $r(sys.media.ohos_ic_public_camera) aboutToAppear() { ImagePickerUtil.setContext(getContext(this)) } // 选择头像 async selectAvatar() { // 压缩限制宽高500*500画质75 const compress { width: 500, height: 500, quality: 75 } const imgRes await ImagePickerUtil.pickSingleImage(compress) if (!imgRes || !imgRes.pixelMap) return // 保存压缩图到沙盒 const savePath await ImagePickerUtil.savePixelToSandbox(imgRes.pixelMap, avatar_temp) if (!savePath) return this.avatarUri imgRes.uri // 调用上传接口 await HttpUtil.uploadFile(/upload/avatar, savePath, avatar.jpg) } build() { Column({ space: 20 }) { Image(this.avatarUri) .width(120) .height(120) .borderRadius(60) Button(更换头像相册单选) .width(300) .onClick(() this.selectAvatar()) } .width(100%) .height(100%) .justifyContent(Center) } }3.2 发布动态页面相册多选 9 张ets// 最多选9张图 async function pickMultiImg() { const imgList await ImagePickerUtil.pickMultiImage(9, { width: 800, height: 800, quality: 70 }) console.info(选中图片数量, imgList.length) // 循环上传 for (const img of imgList) { if (!img.pixelMap) continue const path await ImagePickerUtil.savePixelToSandbox(img.pixelMap, dynamic_ Date.now()) await HttpUtil.uploadFile(/dynamic/upload, path, img.jpg) } }3.3 保存网络图片到本地相册ets// 下载图片后存入公共相册 async function saveNetImage(localSandboxPath: string) { const ok await ImagePickerUtil.saveImageToAlbum(localSandboxPath) if (ok) { promptAction.showToast({ message: 图片已保存到相册 }) } }四、开发编码规范4.1 权限规范打开相册自动校验 READ_MEDIA_IMAGES WRITE_MEDIA拍照仅校验 CAMERA保存到相册单独校验 WRITE_MEDIA无权限拦截保存操作。4.2 图片内存优化规范从相册原图必须压缩后再解码、上传防止 PixelMap 大图 OOM 闪退头像类小图限制 500px 以内动态配图限制 800px使用完毕 ImageSource、PixelMap、ImagePacker 必须 release 释放图像内存。4.3 沙盒路径规范临时压缩图片统一存入this.ctx.filesDir私有沙盒不占用公共相册用户主动保存操作才写入 mediaLibrary 公共相册临时图片文件名增加时间戳避免覆盖。4.4 页面生命周期规范页面 aboutToAppear 注入上下文页面退出无需手动关闭选择器API23 页面销毁自动终止 picker 弹窗。五、高频问题与解决方案问题 1选择大图应用闪退、内存溢出 解决读取原图后立即使用 decode 压缩缩小尺寸释放原始 ImageSource。问题 2保存图片到相册失败 解决检查是否声明 WRITE_MEDIA 权限、调用 saveImageToAlbum 前校验权限。问题 3多选图片只返回一张 解决创建 PhotoPickerOptions 时开启 isMultiSelect 并设置 maxSelectNumber。问题 4升级 API23 旧 PhotoAccessHelper 代码报错 解决全部替换为 ohos.file.picker 标准 PhotoPicker。问题 5图片 uri 无法直接在 Image 组件渲染 解决优先使用返回的 uri 字段赋值 Image沙盒 file 路径需转换资源标识。六、总结ImagePickerUtil 整合相册单选 / 多选、相机拍照、图片压缩、沙盒存储、相册保存全套媒体图片能力深度联动前文 PermissionUtil 权限工具自动处理权限校验、永久拒绝引导弹窗统一封装压缩逻辑解决大图内存溢出问题。 工具完全适配 API23可直接搭配网络上传 HttpUtil、弹窗 DialogUtil、权限 PermissionUtil 组合使用是头像上传、朋友圈动态、图片笔记、文件上传类功能必备底层媒体工具。

相关新闻

OpenHarmony 权限统一封装 PermissionUtil(动态权限申请全套方案 API23+)

OpenHarmony 权限统一封装 PermissionUtil(动态权限申请全套方案 API23+)

2026/7/17 0:50:58

摘要鸿蒙将权限分为安装时静态权限与运行时动态敏感权限,相机、相册、文件、通知、位置等都必须动态申请,原生权限 API 重复代码多、缺少永久拒绝跳转设置页逻辑、无统一弹窗提示、未做权限状态判断封装。本文基于ohos.accessToken封装全局权限工具类&am…

Chat2DB 实战:从 ChatGPT 写 SQL 到专业 AI 数据库工具

Chat2DB 实战:从 ChatGPT 写 SQL 到专业 AI 数据库工具

2026/7/17 0:50:58

关键词:Chat2DB、数据库管理工具、智能问数、AI SQL、Text2SQL 很多开发者第一次体验 AI 写 SQL,通常是从 ChatGPT 这类通用大模型开始的。输入一句自然语言需求,模型很快给出 SQL 示例,看起来很高效。 但在真实团队里&#xff0…

go: Recursion Algorithm

go: Recursion Algorithm

2026/7/17 0:50:58

/* # 版权所有 2026 ©涂聚文有限公司™ # 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎 # 描述:Recursion Algorithm # Author : geovindu,Geovin Du 涂聚文. # IDE : goLang 2024.3.6 go 26.2 # os …

C#开发的Excel单元格级差异高亮对比工具源码

C#开发的Excel单元格级差异高亮对比工具源码

2026/7/17 2:31:03

本文还有配套的精品资源,点击获取 简介:一个开箱即用的C#桌面端Excel比对程序,能精准识别两个Excel文件之间、或同一文件内不同工作表之间的单元格内容差异,并以颜色标记不一致的行、列和具体单元格。支持.xlsx和.xls格式&…

Windows 64位一键安装的ArangoDB 3.8.4社区版(内置图/文档/键值三模型)

Windows 64位一键安装的ArangoDB 3.8.4社区版(内置图/文档/键值三模型)

2026/7/17 2:31:03

本文还有配套的精品资源,点击获取 简介:专为Windows 64位系统准备的ArangoDB 3.8.4社区版安装包,开箱即用,无需编译或额外配置。包内集成原生支持的三种数据模型:图(Graph)、文档&#xff08…

Agent Skills 是 AI 的职业资格证,不是插件

Agent Skills 是 AI 的职业资格证,不是插件

2026/7/17 2:31:03

1. Agent Skills 不是插件,而是 AI 的“职业资格证”体系很多人第一次听到“Agent Skills”时,下意识会把它类比成浏览器插件、VS Code 扩展或 PyCharm 插件——点一下安装,功能就来了。这种理解偏差,直接导致大量开发者在实操中反…

Claude Context:基于语义搜索的AI编程助手代码库增强工具

Claude Context:基于语义搜索的AI编程助手代码库增强工具

2026/7/17 2:31:03

今天来看一个能让你整个代码库都成为AI编程助手上下文的工具——Claude Context。这是Zilliz开源的MCP插件,专门为Claude Code和其他AI编程助手添加语义代码搜索能力。简单说,Claude Context通过语义搜索技术,让AI助手能够从数百万行代码中快…

公司内的系统用的比较多,AI智能体能不能提高内部数据流通速度?

公司内的系统用的比较多,AI智能体能不能提高内部数据流通速度?

2026/7/17 2:31:03

在数字化转型进入“深水区”的当下,许多企业面临着一个经典的“系统悖论”:拥有的数字化系统越多(ERP、CRM、MES、OA、财务系统等),信息孤岛反而越严重,内部数据流通速度越慢。员工每天穿梭在不同的界面之间…

DocsGPT开源平台:基于RAG技术的企业级智能文档助手部署指南

DocsGPT开源平台:基于RAG技术的企业级智能文档助手部署指南

2026/7/17 2:21:03

如果你正在为团队或企业构建智能助手,却苦于文档格式兼容性差、模型选择受限、部署复杂等问题,那么 DocsGPT 可能正是你需要的解决方案。这个开源项目在 GitHub 上已经获得超过 18k 星标,它不仅仅是一个简单的文档问答工具,而是一…

Unity游戏文本翻译架构深度解析:XUnity.AutoTranslator的技术实现与工程实践

Unity游戏文本翻译架构深度解析:XUnity.AutoTranslator的技术实现与工程实践

2026/7/16 0:35:09

Unity游戏文本翻译架构深度解析:XUnity.AutoTranslator的技术实现与工程实践 【免费下载链接】XUnity.AutoTranslator 项目地址: https://gitcode.com/gh_mirrors/xu/XUnity.AutoTranslator XUnity.AutoTranslator作为Unity游戏社区中最成熟的文本翻译解决方…

openEuler Raspberry Pi Kernel设备驱动开发指南:为树莓派硬件添加支持

openEuler Raspberry Pi Kernel设备驱动开发指南:为树莓派硬件添加支持

2026/7/16 14:29:31

openEuler Raspberry Pi Kernel设备驱动开发指南:为树莓派硬件添加支持 【免费下载链接】raspberrypi-kernel It provides openEuler kernel source for Raspberry Pi 项目地址: https://gitcode.com/openeuler/raspberrypi-kernel 前往项目官网免费下载&…

openEuler系统集成测试实战:基于smoke-test套件的环境验证技巧

openEuler系统集成测试实战:基于smoke-test套件的环境验证技巧

2026/7/16 14:18:17

openEuler系统集成测试实战:基于smoke-test套件的环境验证技巧 【免费下载链接】integration-test The repo contains test suits for system integration test 项目地址: https://gitcode.com/openeuler/integration-test 前往项目官网免费下载:…

Cursor终端插件生态避坑指南:23个实测低效插件黑名单,附3个自研轻量替代方案

Cursor终端插件生态避坑指南:23个实测低效插件黑名单,附3个自研轻量替代方案

2026/7/17 0:00:57

更多请点击: https://intelliparadigm.com 第一章:Cursor终端插件生态避坑指南概览 Cursor 作为基于 VS Code 内核构建的 AI 原生编辑器,其终端插件生态虽活跃,但存在兼容性断层、权限策略突变与调试链路断裂等典型风险。开发者常…

ChatGPT写作提示词效率革命:单条提示词响应质量提升3.8倍的关键变量(附可复用提示词矩阵表)

ChatGPT写作提示词效率革命:单条提示词响应质量提升3.8倍的关键变量(附可复用提示词矩阵表)

2026/7/17 0:00:57

更多请点击: https://codechina.net 第一章:ChatGPT写作提示词效率革命:单条提示词响应质量提升3.8倍的关键变量(附可复用提示词矩阵表) 提示词工程已从经验试错迈入变量驱动的科学阶段。实证研究表明,影响…

DeepSeek V4替换Codex底座模型的实践与优化

DeepSeek V4替换Codex底座模型的实践与优化

2026/7/17 0:00:57

1. 为什么选择用DeepSeek V4替换Codex的底座模型去年我在开发一个智能代码补全工具时,发现Codex的默认底座模型在复杂业务逻辑场景下表现不尽如人意。经过多次测试对比,DeepSeek V4在以下几个关键指标上展现出明显优势:代码补全准确率&#x…