一、HarmonyOS NEXT 相册开发体系概述
在移动应用开发中,相册能力几乎是所有图片类应用的基础能力。
无论是:
- 社交应用上传头像
- 电商应用上传商品图片
- 图片编辑器选择素材
- 云相册同步照片
- 短视频应用选择视频
- 扫描应用读取图片
都需要访问系统媒体资源。
在传统移动平台中,开发者经常通过文件路径访问图片,例如:
/storage/emulated/0/DCIM/Camera/photo.jpg
然后通过文件 API 读取图片,再转换为 Bitmap 进行显示和处理。
但是 HarmonyOS NEXT 并没有采用这种直接访问文件系统的方式,而是提供了一套更加安全、统一的媒体访问框架。
核心就是:
PhotoAccessHelper
|
↓
PhotoAsset
|
↓
Asset URI
|
↓
ImageSource
|
↓
PixelMap
|
↓
图片处理与展示
这套体系将:
- 媒体资源管理
- 图片解析
- 像素处理
- 图片编码
进行了清晰分层。
二、为什么 HarmonyOS 不推荐直接访问图片文件?
很多熟悉 Android 开发的工程师第一次接触 HarmonyOS 相册 API 时,会习惯性思考:
“为什么不能直接拿图片路径?”
实际上,这是现代移动操作系统的发展趋势。
1. 文件路径不是可靠的数据标识
传统模式:
/storage/emulated/0/DCIM/photo.jpg
存在很多问题。
例如:
用户可能:
- 移动图片位置
- 修改文件名称
- 删除文件
- 系统进行媒体整理
路径随时可能变化。
但是照片资源本身应该拥有一个稳定身份。
因此 HarmonyOS 使用 URI 标识媒体资源。
例如:
photo://media/xxxxx
应用关注的是:
“我要访问哪一张图片”
而不是:
“这张图片现在存在哪个路径”。
2. 保护用户隐私
相册属于用户隐私数据。
如果应用可以随意扫描:
DCIM
Pictures
Download
那么任何应用都可能获取用户全部照片。
因此 HarmonyOS 引入:
- 权限控制
- 媒体资源对象
- 系统管理
应用只能访问用户授权的数据。
3. 系统统一管理媒体资源
HarmonyOS 将图片、视频等媒体资源交给系统媒体库管理。
应用不直接管理文件。
整体结构:
System Media Library
|
——————————–
| |
图片 视频
| |
PhotoAccessHelper VideoAccessHelper
这样可以保证:
- 数据一致性
- 权限安全
- 系统优化
三、PhotoAccessHelper 是什么?
PhotoAccessHelper 是 HarmonyOS NEXT 提供的媒体访问入口。
它负责:
- 查询照片
- 查询视频
- 创建媒体资源
- 删除媒体资源
- 获取媒体信息
可以理解为:
PhotoAccessHelper 是应用访问系统相册的管理器。
它本身并不是图片。
它更像数据库访问层。
例如:
数据库:
PhotoAccessHelper
↓
查询媒体表
↓
返回 PhotoAsset
四、PhotoAsset 是什么?
PhotoAsset 是相册开发中最重要的数据对象。
它代表:
“一张媒体资源记录”。
例如用户手机中有:
10000 张照片。
通过 PhotoAccessHelper 查询后,系统返回的是一个 PhotoAsset 集合。
每一个 PhotoAsset 对应一张照片。
关系如下:
PhotoAccessHelper
|
↓
PhotoAsset[]
|
↓
10000 个照片资源对象
但是需要注意:
PhotoAsset 不等于图片数据。
它里面保存的是图片描述信息,例如:
- URI
- 文件名称
- 创建时间
- 修改时间
- 文件大小
- 图片类型
- 宽高信息
类似:
数据库中的一条记录。
真正的图片像素数据,需要继续通过 ImageSource 加载。
五、PhotoAccessHelper、PhotoAsset、PixelMap 三者关系
很多开发者容易混淆这三个对象。
实际上它们处于不同层级。
PhotoAccessHelper
负责:
访问媒体库。
解决:
“去哪找图片?”
PhotoAsset
负责:
表示一张图片资源。
解决:
“我要哪一张图片?”
PixelMap
负责:
表示图片像素。
解决:
“我要如何修改图片?”
完整流程:
系统相册
↓
PhotoAccessHelper
↓
PhotoAsset
↓
ImageSource
↓
PixelMap
↓
裁剪 / 缩放 / 滤镜 / 压缩
六、创建 PhotoAccessHelper 实例
在 ArkTS 中,需要先导入模块:
import photoAccessHelper from '@ohos.file.photoAccessHelper';
然后:
const context = getContext(this);
const helper =
photoAccessHelper.getPhotoAccessHelper(context);
得到的 helper 对象就是后续访问相册的核心对象。
七、HarmonyOS 相册权限体系
访问用户照片之前,需要申请权限。
主要涉及:
读取图片和视频权限
ohos.permission.READ_IMAGEVIDEO
用于:
- 查询照片
- 读取图片
- 获取视频资源
写入图片和视频权限
ohos.permission.WRITE_IMAGEVIDEO
用于:
- 保存图片
- 创建媒体资源
在 module.json5 中声明:
{
"requestPermissions": [
{
"name": "ohos.permission.READ_IMAGEVIDEO"
},
{
"name": "ohos.permission.WRITE_IMAGEVIDEO"
}
]
}
八、动态权限申请流程
仅仅配置权限是不够的。
运行时仍然需要用户授权。
完整流程:
应用启动
↓
检查权限
↓
没有权限
↓
申请权限
↓
用户确认
↓
访问相册
实际开发中,不建议 App 一启动就弹权限框。
更好的方式:
用户点击:
“选择图片”
或者:
“上传头像”
时,再申请权限。
原因:
用户此时明确知道为什么需要权限。
九、PhotoPicker 与 PhotoAccessHelper 区别
这是相册开发中的重点。
很多开发者会把两者混淆。
PhotoPicker
它是:
系统图片选择器。
流程:
应用
↓
打开系统选择页面
↓
用户选择图片
↓
返回结果
特点:
优点:
- 安全
- 简单
- 不需要自己开发图库
缺点:
- 无法管理整个相册
- 无法做复杂图片管理
适合:
上传头像。
PhotoAccessHelper
它是:
媒体库访问 API。
适合:
开发完整相册能力。
例如:
- 图片浏览器
- 图片管理器
- 修图软件
- 云相册
十、相册列表开发核心设计
一个真实相册页面,不应该这样:
查询全部照片
↓
全部转 PixelMap
↓
显示
这是错误设计。
因为:
用户可能有:
几万张照片。
正确架构:
PhotoAccessHelper
↓
查询 PhotoAsset
↓
分页加载
↓
获取 Thumbnail
↓
Grid显示
相册列表只需要:
PhotoAsset + 缩略图。
不要加载原图。
十一、为什么相册列表不能直接加载 PixelMap?
因为 PixelMap 很大。
例如:
一张照片:
4000 × 3000。
RGBA 内存:
约:
4000 × 3000 × 4
≈48MB
如果同时加载:
100 张:
就是:
约 4.8GB。
移动设备根本无法承受。
所以:
相册列表:
使用 Thumbnail。
点击查看:
再加载 PixelMap。
十二、缩略图加载架构
优秀的相册列表采用:
可见区域加载。
例如:
当前屏幕显示:
30 张图片。
那么:
只需要提前加载:
30~50 张缩略图。
而不是加载整个相册。
流程:
用户打开页面
↓
查询 PhotoAsset
↓
计算可见区域
↓
加载 Thumbnail
↓
缓存
↓
显示
十四、PhotoAccessHelper 查询机制深入解析
前面我们了解了 PhotoAccessHelper 的基本定位。
实际项目中,真正复杂的部分并不是创建 PhotoAccessHelper,而是如何高效查询媒体资源。
一个简单 Demo 可能只有几十张图片,但是企业级应用面对的是:
- 用户几千张照片
- 用户几万张照片
- 多相册分类
- 时间排序
- 类型筛选
- 分页加载
- 快速滚动
因此,相册查询系统必须具备高性能。
1. 相册查询本质是什么?
很多开发者认为:
查询相册就是:
“读取图片列表”。
实际上并不是。
HarmonyOS 的相册体系本质上是:
应用通过 PhotoAccessHelper 查询系统媒体数据库。
流程:
应用
↓
PhotoAccessHelper
↓
Media Library
↓
媒体数据库
↓
PhotoAsset集合
↓
应用展示
系统内部维护的是媒体索引。
例如:
一张照片可能包含:
| URI | photo://media/xxx |
| 名称 | IMG_001.jpg |
| 类型 | IMAGE |
| 大小 | 4MB |
| 宽度 | 4000 |
| 高度 | 3000 |
| 创建时间 | 2026-08-02 |
应用查询时,并不是扫描磁盘文件。
而是在查询系统已经建立好的媒体索引。
十五、为什么媒体数据库比文件扫描快?
假设手机中有:
20000 张照片。
传统方式:
扫描目录
↓
读取文件信息
↓
判断是否图片
↓
排序
↓
返回结果
这个过程非常慢。
媒体数据库方式:
查询索引
↓
返回记录
类似数据库:
SELECT *
FROM photo
ORDER BY createTime DESC
LIMIT 50;
速度会快很多。
十六、相册查询不要一次加载全部数据
这是企业开发中最重要的原则。
错误:
打开相册
↓
查询全部10000张图片
↓
保存数组
↓
显示
问题:
正确:
分页查询。
例如:
第一次:
加载50张
用户滑动:
继续加载50张
形成:
无限滚动。
结构:
第一页
PhotoAsset[0-49]
第二页
PhotoAsset[50-99]
第三页
PhotoAsset[100-149]
十七、相册分页架构设计
推荐:
Repository 层负责分页。
例如:
GalleryPage
|
↓
GalleryViewModel
|
↓
PhotoRepository
|
↓
PhotoAccessHelper
ViewModel 不应该直接调用:
PhotoAccessHelper。
原因:
页面不应该知道底层媒体访问逻辑。
例如:
错误:
@Component
struct Gallery {
helper.getAssets()
}
页面直接依赖系统 API。
后期:
很难维护。
正确:
UI
↓
ViewModel
↓
Repository
↓
PhotoAccessHelper
十八、PhotoAsset 分页数据管理
假设:
第一页返回:
50个 PhotoAsset。
保存:
let assets: PhotoAsset[] = [];
追加:
第一次:
assets:
[
photo1,
photo2,
…
photo50
]
第二次:
追加:
photo51-photo100
但是需要注意:
不要无限保存。
例如:
用户浏览:
10000张图片。
全部保存在内存:
没有必要。
可以采用:
窗口缓存。
例如:
只保存:
最近浏览的:
200~500个对象。
十九、DataSharePredicates 条件查询
HarmonyOS 查询媒体资源时,可以通过条件过滤。
例如:
只查询图片。
只查询最近照片。
只查询某个时间范围。
为什么需要条件查询?
因为:
媒体库数据可能非常大。
例如:
用户:
有:
50000张照片。
但是页面:
只需要:
最近100张。
应该:
让系统过滤。
而不是:
应用拿全部数据后过滤。
流程:
错误:
50000张数据
↓
应用过滤
↓
剩余100张
正确:
查询条件
↓
系统过滤
↓
返回100张
二十、按照时间排序相册
用户相册通常按照:
创建时间倒序。
也就是:
最新照片显示在最前面。
例如:
2026-08-02 20:30
2026-08-01 18:20
2026-07-30 12:10
原因:
符合用户习惯。
打开相册:
第一眼看到:
最近拍摄内容。
二十一、Album 相册分类管理
除了全部照片:
系统还存在:
相册分类。
例如:
- Camera
- Screenshots
- Downloads
- 微信图片
- 自定义相册
在 HarmonyOS 中:
Album 可以理解为:
照片集合。
结构:
Album
|
|
PhotoAsset[]
例如:
Camera 相册:
Camera Album
|
├── photo1
├── photo2
└── photo3
二十二、为什么需要 Album?
如果应用只展示全部照片:
用户体验不好。
例如:
用户有:
10000张图片。
其中:
截图:
3000张。
聊天图片:
4000张。
相机照片:
3000张。
用户通常希望:
快速进入:
“相机照片”。
因此:
Album 是相册应用的重要能力。
二十三、自定义相册设计
很多图片应用都有:
创建文件夹。
例如:
“旅行照片”
“工作资料”
“设计素材”
逻辑:
不是复制图片。
而是:
创建媒体集合关系。
类似:
数据库:
Album
|
|
PhotoAsset
一张图片:
可以属于:
不同逻辑分类。
二十四、相册列表性能优化核心
一个优秀的相册页面,需要解决三个问题:
第一:查询速度
解决:
分页查询。
第二:显示速度
解决:
Thumbnail。
第三:内存控制
解决:
缓存策略。
整体:
PhotoAccessHelper
↓
分页PhotoAsset
↓
Thumbnail加载
↓
LRU缓存
↓
Grid显示
二十五、Thumbnail 为什么是相册性能关键?
图片加载主要有两个阶段:
第一阶段:
解码。
第二阶段:
渲染。
原图:
4000×3000。
解码成本非常高。
缩略图:
200×200。
成本低很多。
所以:
列表永远:
优先缩略图。
二十六、Thumbnail 缓存设计
如果没有缓存:
用户滑动:
过程:
向下滑
加载图片
向上滑
重新加载
大量重复工作。
加入缓存:
URI
↓
Thumbnail
再次显示:
直接:
从缓存获取。
常见策略:
LRU。
Least Recently Used。
例如:
缓存:
200张缩略图。
超过:
删除最久未使用。
二十七、相册滚动优化
大型相册:
最容易出现:
问题:
快速滑动卡顿。
原因:
滑动过程中:
不断:
创建图片对象。
优化:
1. 复用组件
避免重复创建。
2. 控制并发加载数量
不要:
同时加载100张。
3. 优先加载可见区域
当前屏幕优先级最高。
例如:
用户正在看:
第500张。
那么:
500附近图片优先。
二十八、企业级相册数据流
完整架构:
用户打开相册
↓
GalleryPage
↓
ViewModel
↓
PhotoRepository
↓
PhotoAccessHelper
↓
PhotoAsset分页数据
↓
Thumbnail Manager
↓
Image组件显示
这个架构可以支持:
- 万级图片
- 快速滚动
- 图片缓存
- 后台加载
三十、PhotoAsset 获取图片数据完整流程
前面我们已经了解:
- PhotoAccessHelper 负责访问系统媒体库
- PhotoAsset 代表媒体资源
- Thumbnail 用于相册列表展示
但是当用户点击一张图片,需要进入:
- 图片预览
- 图片编辑
- 图片压缩
- 图片上传
场景时,就不能继续使用缩略图。
这时候需要获取真正的图片数据。
完整流程:
PhotoAsset
↓
打开媒体资源
↓
获取文件描述符
↓
ImageSource
↓
PixelMap
↓
图片处理
这里有一个非常重要的概念:
PhotoAsset 本身不是图片。
它只是图片资源的一个引用。
例如:
PhotoAsset
{
uri: "photo://media/xxx",
name: "IMG_001.jpg",
size: 5242880
}
它告诉系统:
“我要访问哪张图片”。
但是:
真正的 JPEG 二进制数据,需要通过媒体接口读取。
三十一、PhotoAsset 获取资源句柄
在 HarmonyOS 中,访问媒体资源通常需要通过 PhotoAsset 提供的方法获取资源访问能力。
流程:
PhotoAsset
↓
open()
↓
文件描述符
↓
ImageSource
为什么不是直接传 URI 给 ImageSource?
因为:
URI 是资源标识。
而 ImageSource 需要:
可读取的数据源。
两者职责不同。
类似:
数据库:
用户ID
↓
查询用户
↓
获取用户数据
不能:
直接拿 ID 当用户对象。
三十二、ImageSource 在相册中的作用
ImageSource 是图片解析入口。
它负责:
- 读取图片编码数据
- 解析 JPEG
- 解析 PNG
- 获取图片信息
- 创建 PixelMap
完整关系:
JPEG文件
↓
ImageSource
↓
PixelMap
例如:
一张照片:
IMG_001.jpg
内部:
其实是:
压缩后的 JPEG 数据。
程序不能直接修改 JPEG 文件中的像素。
必须:
先解码。
解码过程:
JPEG压缩数据
↓
JPEG Decoder
↓
RGBA像素数据
↓
PixelMap
三十三、为什么图片编辑必须经过 PixelMap?
这是 HarmonyOS 图片开发中的核心。
很多人认为:
“我要裁剪图片,直接操作文件不行吗?”
答案:
不行。
原因:
JPEG、PNG 是编码格式。
例如:
JPEG 文件:
压缩数据
DCT变换
量化
编码
它不是:
二维像素数组。
而 PixelMap:
才是真正:
像素矩阵
例如:
PixelMap
[
[R,G,B,A],
[R,G,B,A],
[R,G,B,A]
]
所以:
所有图片处理:
最终都会转换到 PixelMap。
包括:
- 裁剪
- 旋转
- 缩放
- 滤镜
- 马赛克
- AI增强
三十四、从 PhotoAsset 创建 PixelMap
完整链路:
用户选择图片
↓
PhotoAsset
↓
ImageSource
↓
createPixelMap()
↓
PixelMap
示意代码:
import image from '@ohos.multimedia.image';
const imageSource =
image.createImageSource(fd);
const pixelMap =
await imageSource.createPixelMap();
得到:
PixelMap 后:
就进入:
图片处理阶段。
三十五、大图片加载问题
这是企业开发必须考虑的问题。
例如:
用户手机拍摄:
一张:
8000×6000
照片。
如果直接:
createPixelMap()
可能:
占用大量内存。
计算:
RGBA:
每像素:
4字节。
那么:
8000×6000:
8000 × 6000 × 4
=192000000 bytes
约:
183MB。
一张照片:
就可能接近:
200MB。
如果同时:
打开多张。
容易:
内存压力。
三十六、大图解码优化
正确方式:
不要默认加载完整尺寸。
例如:
用户只是预览。
屏幕:
1080P。
那么:
没有必要加载:
8000×6000。
应该:
缩放解码。
例如:
目标:
1080 × 810
流程:
原图
↓
ImageSource
↓
设置目标尺寸
↓
生成缩小PixelMap
优势:
大幅降低:
- 内存
- 解码时间
- CPU消耗
三十七、相册预览页面设计
一个优秀图片预览页:
不会:
打开页面马上加载原图。
流程:
第一阶段:
显示缩略图。
Thumbnail
↓
快速出现
第二阶段:
后台加载大图。
PixelMap
↓
替换缩略图
用户体验:
类似:
云相册。
架构:
进入页面
↓
显示Thumbnail
↓
后台Decode
↓
PixelMap完成
↓
更新UI
三十八、图片旋转处理
手机拍照:
经常存在方向问题。
例如:
手机横拍:
实际像素:
可能:
4032×3024。
但是:
Exif:
记录:
旋转90度。
所以:
显示前:
需要处理方向。
流程:
ImageSource
↓
读取Orientation
↓
旋转PixelMap
↓
显示
否则:
可能出现:
图片倒置。
三十九、图片裁剪实现思路
例如:
头像裁剪。
用户选择:
一张照片。
然后:
拖动裁剪框。
流程:
PhotoAsset
↓
PixelMap
↓
计算裁剪区域
↓
创建新的PixelMap
↓
显示
裁剪本质:
就是:
复制像素区域。
例如:
原图:
4000×3000
裁剪:
中心区域:
1000×1000
生成:
新的 PixelMap。
四十、图片压缩流程
相册应用经常需要:
上传图片。
例如:
头像:
限制:
500KB。
流程:
PhotoAsset
↓
PixelMap
↓
Resize
↓
ImagePacker
↓
JPEG/WebP
↓
上传
注意:
压缩不是简单:
降低质量。
通常:
包含:
两个步骤。
第一步:尺寸压缩
例如:
4000×3000
变成:
1200×900。
第二步:编码压缩
例如:
JPEG Quality:
80。
这样:
质量和大小:
更加平衡。
四十一、图片编辑器架构设计
如果开发一个类似:
美图秀秀:
的图片编辑功能。
架构:
PhotoAsset
↓
ImageLoader
↓
PixelMap
↓
Editor Engine
↓
Operation Stack
↓
ImagePacker
↓
保存
其中:
Editor Engine:
负责:
各种编辑能力。
Operation Stack:
记录:
操作历史。
例如:
裁剪
↓
旋转
↓
滤镜
↓
撤销
四十二、撤销与重做设计
图片编辑:
必须支持:
Undo / Redo。
错误方式:
每一步保存完整 PixelMap。
例如:
10步操作。
10张:
5000×5000图片。
内存巨大。
正确:
保存操作。
例如:
Operation:
Crop(x,y,w,h)
Rotate(90)
Filter("warm")
恢复时:
重新执行。
这也是:
专业图片编辑器:
常用方案。
四十三、PhotoAccessHelper 与图片编辑完整闭环
现在整个流程:
已经完整:
串起来:
用户选择照片
↓
PhotoAccessHelper
↓
PhotoAsset
↓
ImageSource
↓
PixelMap
↓
编辑处理
↓
ImagePacker
↓
生成新图片
↓
PhotoAccessHelper保存
这就是:
HarmonyOS NEXT 图片应用的完整生命周期。
四十四、创建图片资源:将图片保存到系统相册
前面我们已经完成了图片读取和编辑流程:
PhotoAsset
↓
ImageSource
↓
PixelMap
↓
图片处理
但是一个完整的图片应用不仅需要读取图片,还需要将处理后的结果保存回系统相册。
例如:
- 图片编辑器保存图片
- 拍照应用保存照片
- 海报生成器导出图片
- 截图工具保存截图
这些场景最终都需要创建新的媒体资源。
在 HarmonyOS NEXT 中,应用不能简单地:
创建文件
↓
写入 DCIM 文件夹
这种方式绕过系统媒体管理。
正确方式:
通过:
PhotoAccessHelper
↓
createAsset()
↓
写入图片数据
↓
系统媒体库管理
整体流程:
编辑后的 PixelMap
↓
ImagePacker编码
↓
JPEG/PNG数据
↓
PhotoAccessHelper创建资源
↓
写入媒体库
↓
系统相册显示
四十五、为什么保存图片必须经过 PhotoAccessHelper?
很多开发者会问:
“我已经有 JPEG 数据了,为什么不能直接保存文件?”
原因是:
系统相册不是普通文件夹。
它维护:
- 媒体索引
- 缩略图
- 元数据
- 权限信息
- 相册关系
如果直接写文件:
可能出现:
文件存在
但是
系统相册看不到
例如:
应用执行:
写入:
/Pictures/test.jpg
文件确实存在。
但是:
媒体库不知道:
“这里新增了一张图片”。
通过 PhotoAccessHelper:
系统会自动完成:
创建记录
↓
保存媒体信息
↓
更新索引
↓
通知图库
四十六、创建图片资源流程
完整过程:
分为三个阶段。
第一阶段:创建媒体记录
告诉系统:
我要保存一张图片。
例如:
名称:
edit_photo.jpg
类型:
IMAGE
第二阶段:写入图片数据
将:
JPEG/PNG二进制数据
写入资源。
第三阶段:完成提交
系统更新:
媒体库。
流程:
createAsset
↓
open
↓
write
↓
close
↓
图库刷新
四十七、ImagePacker 编码图片
PixelMap 是内存中的像素。
系统相册不能直接保存 PixelMap。
必须编码。
例如:
PixelMap:
RGBA像素
↓
ImagePacker
↓
JPEG
↓
二进制数据
为什么需要编码?
因为:
PixelMap:
适合程序处理。
JPEG:
适合存储。
关系:
PixelMap
(内存格式)
↓
ImagePacker
↓
JPEG
(文件格式)
四十八、JPEG 与 PNG 如何选择?
保存图片时:
格式选择很重要。
JPEG
适合:
- 相机照片
- 风景照片
- 人像照片
特点:
文件小。
但是:
有损压缩。
例如:
原图:
10MB。
JPEG:
可能:
1MB。
PNG
适合:
- 截图
- UI图片
- 透明图片
特点:
无损。
支持透明。
例如:
Logo:
需要:
Alpha通道。
WebP
现代应用常用。
优势:
在相同质量下:
比 JPEG 更小。
适合:
移动端上传。
四十九、保存编辑图片完整案例
假设:
用户:
选择图片。
然后:
添加滤镜。
点击:
保存。
流程:
用户点击保存
↓
获取编辑后的PixelMap
↓
ImagePacker编码
↓
生成JPEG数据
↓
PhotoAccessHelper创建Asset
↓
写入数据
↓
系统相册出现新图片
这个流程:
就是:
图片编辑 App 的核心闭环。
五十、批量导入图片设计
很多应用需要:
批量保存。
例如:
- 云盘下载100张图片
- 聊天图片导出
- 相册备份恢复
错误方式:
100张图片
↓
100个保存任务同时执行
问题:
- CPU压力大
- IO竞争
- 内存上涨
正确:
任务队列。
例如:
Task Queue
|
|
Worker
|
|
PhotoAccessHelper
控制并发:
例如:
同时处理:
3~5张。
五十一、批量保存性能优化
批量图片保存:
主要瓶颈:
1. 编码
PixelMap → JPEG
2. IO写入
数据保存。
3. 媒体库更新
系统索引。
优化策略:
第一
降低无意义尺寸。
例如:
原图:
6000×4000。
上传:
只需要:
1200×800。
第二
统一后台处理。
不要阻塞 UI。
第三
控制任务数量。
五十二、删除图片资源
相册应用经常需要:
删除图片。
例如:
用户点击:
删除按钮。
HarmonyOS 不推荐:
直接删除文件。
正确:
通过:
PhotoAccessHelper
删除媒体资源。
流程:
用户选择图片
↓
获取PhotoAsset
↓
deleteAssets()
↓
系统确认
↓
媒体库删除
为什么?
因为删除不仅仅是删除文件。
还需要:
同步:
- 数据库
- 缩略图
- 索引
- 相册关系
五十三、删除图片后的状态处理
删除之后:
应用需要刷新。
例如:
当前列表:
photo1
photo2
photo3
用户删除:
photo2。
需要:
更新:
photo1
photo3
不要:
等待重新查询整个相册。
更好的方式:
局部更新。
流程:
删除成功
↓
移除对应PhotoAsset
↓
刷新列表
五十四、相册回收站设计
现代系统通常不会立即永久删除。
例如:
照片 App:
删除:
进入:
最近删除。
应用如果实现类似功能:
需要设计:
状态。
例如:
正常
↓
删除标记
↓
回收站
↓
永久删除
数据库思想:
增加:
状态字段。
例如:
status:
normal
deleted
五十五、权限最佳实践
相册权限是用户信任的重要部分。
不要:
App启动:
立即申请。
推荐:
用户行为触发。
例如:
用户点击:
“上传图片”。
此时:
申请:
读取权限。
流程:
点击上传
↓
解释用途
↓
申请权限
↓
用户允许
↓
打开相册
五十六、异常处理设计
真实环境中:
必须考虑:
失败情况。
例如:
权限拒绝
用户点击:
“不允许”。
处理:
显示提示。
图片被删除
用户查询后:
系统删除。
处理:
重新刷新。
文件损坏
ImageSource解析失败。
处理:
跳过。
内存不足
大图加载失败。
处理:
降低解码尺寸。
五十七、图片上传前处理架构
很多业务:
不是保存。
而是:
上传服务器。
例如:
头像上传。
完整流程:
PhotoAsset
↓
PixelMap
↓
压缩
↓
ImagePacker
↓
Base64 / File
↓
HTTP上传
不要:
直接上传原图。
原因:
- 流量大
- 上传慢
- 服务器压力高
五十八、企业级图片上传策略
推荐:
客户端:
负责:
第一次压缩。
服务器:
负责:
最终处理。
架构:
手机端
PhotoAccessHelper
↓
PixelMap
↓
压缩
↓
上传
服务器
↓
二次处理
↓
CDN
这样:
兼顾:
速度和质量。
六十一、企业级相册整体架构
一个成熟的 HarmonyOS 相册模块通常分为多个层级。
推荐架构:
Gallery UI
|
↓
Gallery ViewModel
|
↓
Photo Repository
|
↓
PhotoAccessService
|
↓
PhotoAccessHelper
|
↓
System Media Library
每一层职责不同。
UI 层
负责:
- 图片展示
- Grid布局
- 用户交互
- 滚动状态
不负责:
- 查询相册
- 图片解码
- 文件处理
ViewModel 层
负责:
- 页面状态管理
- 加载状态
- 分页控制
- 错误处理
Repository 层
负责:
- 数据来源统一
- 缓存管理
- 查询封装
PhotoAccessService 层
负责:
- HarmonyOS API封装
- 权限处理
- 媒体访问
六十二、为什么不能让 UI 直接调用 PhotoAccessHelper?
很多 Demo 会这样写:
页面
↓
PhotoAccessHelper
↓
查询图片
小项目没有问题。
但是大型项目会产生大量问题。
例如:
页面代码:
@Component
struct Gallery {
loadPhotos(){
helper.getAssets()
}
}
问题:
正确:
UI
↓
ViewModel
↓
Repository
↓
PhotoAccessHelper
这样:
未来增加:
- 云端图片
- 网络相册
- 本地缓存
都容易扩展。
六十三、百万级图片查询策略
真实用户:
可能有:
几万张照片。
企业应用:
甚至:
管理百万级图片资源。
核心原则:
不要加载全部。
错误:
查询100000张
↓
保存数组
↓
显示
正确:
采用:
分页 + 游标。
例如:
第一次:
offset = 0
limit = 50
返回:
50张。
第二次:
offset = 50
limit = 50
继续加载。
形成:
流式加载。
六十四、相册数据分页状态管理
ViewModel 中:
通常维护:
class GalleryState {
assets: PhotoAsset[];
page: number;
loading: boolean;
hasMore: boolean;
}
例如:
初始:
page = 1
assets = []
loading = false
用户打开页面:
loading=true
↓
查询第一页
↓
追加数据
↓
loading=false
用户滑到底:
继续:
page++
↓
加载下一页
六十五、Thumbnail 加载系统设计
相册体验好不好,核心不是查询。
而是图片显示速度。
用户打开相册:
希望:
马上看到图片。
所以:
需要:
Thumbnail Manager。
架构:
PhotoAsset
|
↓
ThumbnailManager
|
├── Memory Cache
|
└── Disk Cache
|
↓
Image Component
六十六、两级图片缓存设计
大型图片应用:
通常使用:
两级缓存。
一级缓存:内存缓存
速度最快。
保存:
最近使用图片。
例如:
URI
↓
PixelMap缩略图
优点:
访问速度:
毫秒级。
缺点:
占用内存。
二级缓存:磁盘缓存
保存:
已经生成的缩略图文件。
例如:
cache/
photo1.thumb
photo2.thumb
优点:
退出应用后仍然存在。
缺点:
读取慢一些。
整体:
请求图片
↓
Memory Cache
↓
Disk Cache
↓
PhotoAccessHelper
六十七、LRU缓存算法
内存有限。
不能无限保存图片。
所以采用:
LRU:
Least Recently Used。
意思:
最近最少使用。
例如:
缓存容量:
100张。
当前:
A B C D E
用户访问:
A。
变成:
B C D E A
当加入:
F。
删除:
最久未访问:
B。
相册应用非常适合 LRU。
六十八、图片加载线程模型
图片加载不能阻塞 UI。
错误:
UI线程
↓
读取图片
↓
解码
↓
显示
结果:
页面卡顿。
正确:
UI线程
|
↓
任务线程
|
↓
Image Decode
|
↓
返回结果
|
↓
UI更新
六十九、并发加载控制
很多人认为:
线程越多越快。
实际上:
图片解码属于 CPU 密集型任务。
例如:
同时:
启动:
50个图片解码。
结果:
CPU满载。
正确:
限制并发数量。
例如:
任务队列
|
|
Worker1
Worker2
Worker3
通常:
根据设备性能动态调整。
七十、大图片内存优化
图片应用最大的问题:
就是内存。
例如:
一张:
6000×4000照片。
RGBA:
计算:
6000 × 4000 × 4
≈96MB
如果同时:
加载5张:
接近:
500MB。
所以:
必须:
控制。
七十一、图片解码尺寸控制
不要:
默认加载原图。
例如:
用户查看预览。
屏幕:
1080P。
原图:
6000×4000。
没必要。
应该:
根据目标显示尺寸:
计算采样比例。
例如:
目标:
1200×800。
那么:
解码:
接近这个尺寸即可。
优势:
明显降低:
内存。
七十二、快速滑动优化
相册列表:
快速滑动时:
最容易卡顿。
原因:
大量图片请求。
优化:
第一
取消不可见任务。
例如:
用户快速滑过:
第100张。
还没加载完成。
但是用户已经到:
第500张。
那么:
第100张任务应该取消。
第二
提高当前区域优先级。
例如:
当前屏幕:
优先加载。
第三
降低后台任务。
七十三、图片选择器设计
类似:
朋友圈:
选择图片。
通常:
支持:
- 多选
- 数量限制
- 已选状态
- 原图选择
数据结构:
interface SelectedPhoto {
asset: PhotoAsset;
selected:boolean;
order:number;
}
为什么保存:
PhotoAsset?
因为:
后续需要:
获取原图。
不要保存:
PixelMap。
七十四、多选图片性能问题
用户选择:
50张图片。
不能:
立即全部加载原图。
正确:
选择阶段:
保存:
PhotoAsset。
上传阶段:
逐个处理:
PhotoAsset1
↓
PixelMap
↓
压缩
↓
上传
PhotoAsset2
↓
PixelMap
↓
压缩
↓
上传
避免:
瞬间占用大量内存。
七十五、类似微信朋友圈图片选择器架构
一个完整图片选择器:
如下:
PhotoPickerPage
|
↓
PhotoGrid
|
↓
ThumbnailManager
|
↓
SelectionManager
|
↓
UploadPipeline
SelectionManager:
负责:
- 已选数量
- 排序
- 删除选择
UploadPipeline:
负责:
- 加载
- 压缩
- 上传
七十六、相册模块错误处理体系
生产环境必须处理异常。
权限异常
用户拒绝。
处理:
引导授权。
图片不存在
例如:
用户删除。
处理:
刷新列表。
图片损坏
ImageSource失败。
处理:
跳过。
内存不足
降低解码尺寸。
七十七、相册生命周期管理
页面退出:
必须释放资源。
例如:
PixelMap。
不用后:
释放。
缓存:
根据策略清理。
后台:
暂停非必要任务。
流程:
页面进入
↓
创建资源
↓
使用
↓
页面退出
↓
释放
七十八、企业级相册最终架构
完整:
Gallery UI
|
↓
ViewModel
|
↓
Repository
|
↓
PhotoAccessService
|
↓
PhotoAccessHelper
|
↓
Media Library
图片处理:
PhotoAsset
↓
ImageSource
↓
PixelMap
↓
ImagePacker
↓
保存/上传
八十、PhotoAccessHelper 底层原理分析
前面我们主要站在应用开发角度,介绍了如何使用 PhotoAccessHelper 完成:
- 查询图片
- 展示相册
- 加载缩略图
- 获取原图
- 保存图片
- 删除资源
但是如果想真正掌握 HarmonyOS NEXT 相册体系,还需要理解它底层的工作机制。
PhotoAccessHelper 并不是简单的一个文件读取工具。
它实际上连接的是:
应用层 → 媒体访问框架 → 系统媒体库 → 文件存储系统
整个链路。
八十一、HarmonyOS 媒体访问整体架构
完整结构如下:
应用层
Gallery App
图片编辑器
上传组件
↓
PhotoAccessHelper
↓
Media Library Framework
↓
DataShare 数据访问层
↓
Media Database
↓
文件系统
↓
真实图片文件
其中:
应用并不会直接操作底层文件。
所有媒体资源访问,都经过系统媒体服务。
这样可以保证:
- 权限控制
- 数据一致性
- 媒体索引同步
- 安全隔离
八十二、Media Library 的作用
HarmonyOS 系统中,照片和视频并不是简单存在一个目录中。
系统维护了一套媒体数据库。
例如:
一张照片:
文件:
IMG_001.jpg
同时对应数据库记录:
{
uri:"photo://media/001",
name:"IMG_001.jpg",
type:"image/jpeg",
width:4000,
height:3000,
createTime:xxx
}
所以:
照片实际上有两个部分:
第一部分:
媒体数据。
也就是:
JPEG 文件。
第二部分:
媒体索引。
也就是:
数据库记录。
关系:
Media Database
|
|
PhotoAsset
|
|
Image File
八十三、为什么 PhotoAsset 不是普通对象?
很多开发者第一次使用:
PhotoAsset。
会认为:
它就是:
图片对象。
实际上不是。
PhotoAsset 更像:
数据库 Entity。
类似:
Java 中:
class UserEntity {
id;
name;
}
PhotoAsset:
代表:
媒体库中的一条记录。
例如:
PhotoAsset
id:
123456
uri:
photo://media/123456
name:
test.jpg
它本身不包含:
完整图片二进制。
所以:
以下操作是不合理的:
保存大量PhotoAsset对象
↓
永久缓存
因为:
它只是媒体资源引用。
八十四、PhotoAsset 生命周期
PhotoAsset 生命周期:
通常分为几个阶段。
第一阶段:查询产生
应用查询:
PhotoAccessHelper
↓
PhotoAsset
此时:
对象只是:
资源描述。
第二阶段:使用
例如:
获取:
- 名称
- URI
- 时间
第三阶段:访问数据
例如:
打开图片。
进入:
PhotoAsset
↓
ImageSource
↓
PixelMap
第四阶段:释放
页面关闭。
释放:
相关资源。
八十五、URI 机制深入理解
HarmonyOS 使用 URI 标识媒体资源。
例如:
photo://media/xxxxxxxx
URI 的意义:
不是告诉你:
文件在哪里。
而是告诉系统:
我要访问哪个资源。
类似:
互联网:
https://example.com/user/10086
不是告诉浏览器:
服务器文件路径。
而是:
资源定位。
媒体 URI:
也是同样思想。
八十六、URI 相比文件路径的优势
1. 文件位置可以变化
例如:
系统升级。
存储策略改变。
但是:
URI 可以保持稳定。
2. 权限控制
系统可以判断:
当前应用是否有权访问。
3. 资源统一管理
图片:
视频:
音频:
统一使用资源模型。
八十七、PhotoAccessHelper 查询到底发生了什么?
调用:
helper.getAssets()
表面:
只是一个方法。
内部:
大概流程:
应用
↓
IPC调用
↓
媒体服务
↓
数据库查询
↓
生成Asset对象
↓
返回应用
也就是说:
它不是直接访问数据库。
而是:
跨进程访问系统服务。
因此:
需要考虑:
异步。
八十八、为什么相册查询必须异步?
因为:
查询过程可能涉及:
- IPC通信
- 数据库访问
- 权限检查
- 对象创建
如果同步执行:
可能阻塞 UI。
所以:
现代系统 API:
基本都是:
Promise / Async。
例如:
const assets =
await helper.getAssets(options);
八十九、PhotoAsset 与权限关系
一个重要问题:
为什么查询后拿到 PhotoAsset?
但是打开失败?
原因:
权限可能变化。
例如:
流程:
结果:
访问失败。
所以:
权限不是一次性的。
企业应用需要:
每次关键操作检查。
九十、媒体库变化监听
真实相册应用:
不能只查询一次。
因为:
系统照片随时变化。
例如:
用户:
- 拍照
- 删除
- 下载图片
- 接收聊天图片
媒体库都会变化。
因此:
相册应用需要:
同步机制。
典型:
打开页面
↓
查询当前数据
↓
监听变化
↓
更新列表
九十一、为什么需要媒体变化通知?
假设:
相册页面打开。
当前:
100张照片。
用户退出。
使用系统相机拍了一张。
返回应用。
如果没有更新:
页面还是:
100张。
正确:
应该:
自动出现:
新照片。
九十二、相册增量更新设计
大型应用:
不会每次重新查询全部。
错误:
新增一张照片
↓
重新查询10000张
正确:
增量更新。
例如:
收到:
新增事件。
只插入:
新 PhotoAsset。
数据:
之前:
photo1
photo2
新增:
photo3
更新:
photo3
photo1
photo2
九十三、相册排序稳定性
分页查询中:
排序非常重要。
例如:
第一页:
最新50张。
用户加载第二页。
期间:
新增照片。
如果排序不稳定:
可能出现:
重复。
或者:
遗漏。
所以:
通常需要:
稳定排序字段。
例如:
时间 + ID。
例如:
createTime DESC
id DESC
保证分页一致。
九十四、相册搜索功能设计
大型相册通常支持:
搜索。
例如:
搜索:
“生日”
“旅行”
“截图”
实现方式:
可能依赖:
- 文件名
- 时间
- 标签
- AI识别
PhotoAccessHelper 主要负责:
媒体访问。
智能搜索:
通常在上层实现。
架构:
PhotoAccessHelper
↓
PhotoAsset
↓
Search Engine
↓
搜索结果
九十五、相册与 AI 图片分析结合
HarmonyOS NEXT 时代:
图片应用越来越智能。
例如:
自动分类:
- 人物
- 风景
- 食物
- 文档
流程:
PhotoAsset
↓
读取图片
↓
AI模型分析
↓
生成标签
↓
建立索引
注意:
AI分析通常不应该影响:
基础相册加载。
应该:
后台执行。
九十六、PhotoAccessHelper 性能优化原则
总结核心:
第一
不要批量加载原图。
列表永远使用缩略图。
第二
不要一次查询全部资源。
使用分页。
第三
不要长期保存大量 PhotoAsset。
保持轻量。
第四
不要阻塞 UI。
所有读取异步化。
第五
做好缓存。
减少重复解码。
九十七、完整相册数据流总结
最终完整链路:
用户打开相册
↓
Gallery UI
↓
ViewModel
↓
Repository
↓
PhotoAccessHelper
↓
Media Library
↓
PhotoAsset
↓
Thumbnail
↓
Image组件展示
点击图片:
PhotoAsset
↓
ImageSource
↓
PixelMap
↓
编辑处理
↓
ImagePacker
↓
保存
网硕互联帮助中心






评论前必须登录!
注册