HarmonyOS开发中的媒体库
HarmonyOS开发中的媒体库:@ohos.file.photoAccessHelper、相册管理、媒体文件查询、媒体文件增删改、媒体库权限
核心要点:photoAccessHelper 是 HarmonyOS 媒体库的「大门」,它提供了对系统相册、照片、视频的完整访问能力。掌握权限申请、相册管理、媒体文件 CRUD 操作,是构建相册类、图片编辑类、社交分享类应用的必备技能。
| 项目 | 说明 |
|---|---|
| 核心模块 | @ohos.file.photoAccessHelper |
一、背景与动机
你有没有想过,当你打开手机相册,那些照片是从哪里来的?它们并不是散落在文件系统各处的「野文件」,而是被一个叫做「媒体库」的系统服务统一管理的。
媒体库就像一个超级管家,它知道你手机上每一张照片、每一个视频的来龙去脉——什么时候拍的、在哪里拍的、文件有多大、缩略图在哪。而 photoAccessHelper 就是你和这个管家对话的「窗口」。
为什么不能直接读写文件?因为 HarmonyOS 的安全模型不允许应用随意访问用户数据。你必须通过 photoAccessHelper 这个「合法通道」,在用户授权的前提下,才能操作相册中的媒体文件。
典型的使用场景:
- 相册应用——浏览、管理、编辑照片和视频
- 图片编辑器——读取照片、保存编辑后的照片
- 社交分享——从相册选择照片/视频进行分享
- 文件管理器——查看和管理媒体文件
- 云同步——备份和恢复照片/视频
二、核心原理
2.1 媒体库架构
flowchart TB
classDef primary fill:#1890ff,stroke:#096dd9,color:#fff
classDef warning fill:#fa8c16,stroke:#d48806,color:#fff
classDef error fill:#f5222d,stroke:#cf1322,color:#fff
classDef info fill:#13c2c2,stroke:#006d75,color:#fff
classDef purple fill:#722ed1,stroke:#531dab,color:#fff
subgraph 应用层
A[相册App]:::primary
B[编辑器App]:::primary
C[分享App]:::primary
end
subgraph photoAccessHelper API
D[权限管理]:::warning
E[相册管理]:::warning
F[媒体文件CRUD]:::warning
G[文件URI操作]:::warning
end
subgraph 媒体库服务
H[MediaStore]:::purple
end
subgraph 存储层
I[内部存储]:::info
J[外部存储]:::info
K[云存储]:::info
end
A --> D & E & F
B --> D & F & G
C --> D & F
D --> H
E --> H
F --> H
G --> H
H --> I & J & K
2.2 核心数据模型
媒体库的核心数据模型有三个:
| 概念 | 类名 | 说明 |
|---|---|---|
| 媒体资源 | PhotoAsset |
一张照片或一个视频,包含文件路径、大小、日期等属性 |
| 相册 | Album |
媒体资源的集合,如「相机」「截图」「收藏」等 |
| 用户相册 | UserAlbum |
用户自定义的相册,如「旅行」「美食」等 |
2.3 权限体系
访问媒体库需要申请权限,不同操作需要不同级别的权限:
| 权限 | 级别 | 说明 |
|---|---|---|
ohos.permission.READ_IMAGEVIDEO |
user_granted | 读取照片和视频 |
ohos.permission.WRITE_IMAGEVIDEO |
user_granted | 写入(增删改)照片和视频 |
ohos.permission.READ_PHOTO_ACCESS |
system_granted | 系统级读取权限(仅系统应用) |
⚠️
user_granted类型的权限需要用户在运行时授权,不能静默获取。
三、代码实战
3.1 权限申请与媒体库初始化
这是所有媒体库操作的前提——先申请权限,再获取 phAccessHelper 实例。
import { photoAccessHelper } from '@kit.MediaLibraryKit';
import { abilityAccessCtrl, bundleManager, Permissions } from '@kit.AbilityKit';
import { BusinessError } from '@kit.BasicServicesKit';
/**
* 媒体库权限管理器
* 封装权限申请的完整流程
*/
class MediaPermissionManager {
// 需要申请的权限列表
private readonly REQUIRED_PERMISSIONS: Permissions[] = [
'ohos.permission.READ_IMAGEVIDEO',
'ohos.permission.WRITE_IMAGEVIDEO',
];
/**
* 检查是否已授权
*/
async checkPermissions(): Promise<boolean> {
const atManager = abilityAccessCtrl.createAtManager();
const bundleInfo = await bundleManager.getBundleInfoForSelf(bundleManager.BundleFlag.GET_BUNDLE_INFO_DEFAULT);
const bundleName = bundleInfo.name;
for (const permission of this.REQUIRED_PERMISSIONS) {
const grantStatus = await atManager.checkAccessToken(bundleName, permission);
if (grantStatus !== abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED) {
return false;
}
}
return true;
}
/**
* 请求权限(弹出系统授权对话框)
*/
async requestPermissions(context: Context): Promise<boolean> {
const atManager = abilityAccessCtrl.createAtManager();
try {
const result = await atManager.requestPermissionsFromUser(
context,
this.REQUIRED_PERMISSIONS
);
// 检查所有权限是否都已授权
for (let i = 0; i < result.authResults.length; i++) {
if (result.authResults[i] !== 0) {
console.warn(`[Permission] 权限被拒绝: ${this.REQUIRED_PERMISSIONS[i]}`);
return false;
}
}
console.info('[Permission] 所有权限已授权');
return true;
} catch (err) {
const error = err as BusinessError;
console.error(`[Permission] 请求权限失败: ${error.message}`);
return false;
}
}
/**
* 确保权限已授予(先检查,未授予则请求)
*/
async ensurePermissions(context: Context): Promise<boolean> {
const hasPermission = await this.checkPermissions();
if (hasPermission) return true;
return await this.requestPermissions(context);
}
}
/**
* 获取 phAccessHelper 实例
* 这是访问媒体库的入口
*/
function getPhotoAccessHelper(context: Context): photoAccessHelper.PhotoAccessHelper {
return photoAccessHelper.getPhotoAccessHelper(context);
}
// ========== 使用示例 ==========
const permissionManager = new MediaPermissionManager();
async function initMediaLibrary(context: Context): Promise<photoAccessHelper.PhotoAccessHelper | null> {
const granted = await permissionManager.ensurePermissions(context);
if (!granted) {
console.error('[MediaLib] 权限未授予,无法访问媒体库');
return null;
}
return getPhotoAccessHelper(context);
}
3.2 相册管理与媒体文件查询
查询是媒体库最常用的操作。下面展示如何查询相册列表、查询相册中的媒体文件,以及使用条件筛选。
import { photoAccessHelper } from '@kit.MediaLibraryKit';
import { dataSharePredicates } from '@kit.ArkData';
import { BusinessError } from '@kit.BasicServicesKit';
/**
* 相册与媒体文件查询管理器
*/
class AlbumQueryManager {
private helper: photoAccessHelper.PhotoAccessHelper;
constructor(helper: photoAccessHelper.PhotoAccessHelper) {
this.helper = helper;
}
/**
* 获取所有相册
* 包括系统相册(相机、截图等)和用户相册
*/
async getAllAlbums(): Promise<photoAccessHelper.Album[]> {
try {
// 获取系统相册
const fetchOptions: photoAccessHelper.FetchOptions = {
fetchColumns: [
photoAccessHelper.AlbumKey.ALBUM_ID,
photoAccessHelper.AlbumKey.ALBUM_NAME,
photoAccessHelper.AlbumKey.ALBUM_COUNT,
photoAccessHelper.AlbumKey.ALBUM_COVER_URI,
],
predicates: new dataSharePredicates.DataSharePredicates(),
};
const albumFetchResult = this.helper.getAlbums(
photoAccessHelper.AlbumType.USER, // 用户相册
photoAccessHelper.AlbumSubType.USER_GENERIC, // 通用子类型
fetchOptions
);
const albums: photoAccessHelper.Album[] = [];
const album = await albumFetchResult.getFirstObject();
albums.push(album);
// 遍历剩余相册
while (true) {
try {
const nextAlbum = await albumFetchResult.getNextObject();
albums.push(nextAlbum);
} catch (err) {
// 遍历结束
break;
}
}
console.info(`[Album] 查询到 ${albums.length} 个相册`);
return albums;
} catch (err) {
const error = err as BusinessError;
console.error(`[Album] 查询相册失败: ${error.message}`);
return [];
}
}
/**
* 查询指定相册中的所有照片
*/
async getPhotosInAlbum(album: photoAccessHelper.Album): Promise<photoAccessHelper.PhotoAsset[]> {
try {
const fetchOptions: photoAccessHelper.FetchOptions = {
fetchColumns: [
photoAccessHelper.PhotoKeys.URI,
photoAccessHelper.PhotoKeys.DISPLAY_NAME,
photoAccessHelper.PhotoKeys.DATE_ADDED,
photoAccessHelper.PhotoKeys.DATE_MODIFIED,
photoAccessHelper.PhotoKeys.SIZE,
photoAccessHelper.PhotoKeys.WIDTH,
photoAccessHelper.PhotoKeys.HEIGHT,
photoAccessHelper.PhotoKeys.ORIENTATION,
],
predicates: new dataSharePredicates.DataSharePredicates(),
};
const photoFetchResult = album.getAssets(fetchOptions);
const photos: photoAccessHelper.PhotoAsset[] = [];
const firstPhoto = await photoFetchResult.getFirstObject();
photos.push(firstPhoto);
while (true) {
try {
const nextPhoto = await photoFetchResult.getNextObject();
photos.push(nextPhoto);
} catch (err) {
break;
}
}
console.info(`[Album] 相册 "${album.albumName}" 中有 ${photos.length} 张照片`);
return photos;
} catch (err) {
const error = err as BusinessError;
console.error(`[Album] 查询照片失败: ${error.message}`);
return [];
}
}
/**
* 按条件查询媒体文件
* 支持按类型、日期、大小等条件筛选
*/
async queryPhotosWithFilter(options: {
type?: 'IMAGE' | 'VIDEO' | 'ALL'; // 媒体类型
dateFrom?: number; // 起始日期(时间戳)
dateTo?: number; // 结束日期(时间戳)
limit?: number; // 返回数量限制
}): Promise<photoAccessHelper.PhotoAsset[]> {
try {
// 构建查询条件
const predicates = new dataSharePredicates.DataSharePredicates();
// 按媒体类型筛选
if (options.type && options.type !== 'ALL') {
const mediaType = options.type === 'IMAGE'
? photoAccessHelper.PhotoType.IMAGE
: photoAccessHelper.PhotoType.VIDEO;
predicates.equalTo('media_type', mediaType);
}
// 按日期范围筛选
if (options.dateFrom) {
predicates.greaterThanOrEqualTo('date_added', options.dateFrom);
}
if (options.dateTo) {
predicates.lessThanOrEqualTo('date_added', options.dateTo);
}
// 按添加日期倒序排列(最新的在前)
predicates.orderByDesc('date_added');
// 数量限制
if (options.limit) {
predicates.limit(options.limit);
}
const fetchOptions: photoAccessHelper.FetchOptions = {
fetchColumns: [
photoAccessHelper.PhotoKeys.URI,
photoAccessHelper.PhotoKeys.DISPLAY_NAME,
photoAccessHelper.PhotoKeys.DATE_ADDED,
photoAccessHelper.PhotoKeys.SIZE,
photoAccessHelper.PhotoKeys.WIDTH,
photoAccessHelper.PhotoKeys.HEIGHT,
],
predicates,
};
const fetchResult = this.helper.getAssets(fetchOptions);
const photos: photoAccessHelper.PhotoAsset[] = [];
const first = await fetchResult.getFirstObject();
photos.push(first);
while (true) {
try {
const next = await fetchResult.getNextObject();
photos.push(next);
} catch (err) {
break;
}
}
console.info(`[Query] 查询到 ${photos.length} 个媒体文件`);
return photos;
} catch (err) {
const error = err as BusinessError;
console.error(`[Query] 条件查询失败: ${error.message}`);
return [];
}
}
}
3.3 媒体文件增删改
媒体文件的创建、删除和修改是敏感操作,需要特别小心。下面展示完整的 CRUD 流程。
import { photoAccessHelper } from '@kit.MediaLibraryKit';
import { fileIo } from '@kit.CoreFileKit';
import { BusinessError } from '@kit.BasicServicesKit';
/**
* 媒体文件 CRUD 管理器
*/
class MediaFileManager {
private helper: photoAccessHelper.PhotoAccessHelper;
constructor(helper: photoAccessHelper.PhotoAccessHelper) {
this.helper = helper;
}
// ========== 创建(保存照片到相册) ==========
/**
* 创建一张照片资源
* 典型场景:拍照后保存、编辑后另存为
* @param fileName 文件名(如 'photo_20260620.jpg')
* @param photoType 照片类型(IMAGE 或 VIDEO)
* @param writeFileCallback 写入文件的回调
*/
async createPhotoAsset(
fileName: string,
photoType: photoAccessHelper.PhotoType,
writeFileCallback: (fileUri: string) => Promise<void>
): Promise<photoAccessHelper.PhotoAsset | null> {
try {
// 第一步:创建媒体资源(获得一个安全 URI)
const createOption: photoAccessHelper.CreateOptions = {
title: fileName, // 文件标题
subtype: photoAccessHelper.PhotoSubtype.DEFAULT, // 子类型
};
const assetUri = await this.helper.createAsset(photoType, 'jpg', createOption);
console.info(`[Create] 创建资源成功,URI: ${assetUri}`);
// 第二步:通过 URI 打开文件,写入数据
const file = fileIo.openSync(assetUri, fileIo.OpenMode.WRITE_ONLY);
try {
// 调用回调让调用者写入文件内容
await writeFileCallback(assetUri);
console.info('[Create] 文件写入完成');
} finally {
fileIo.closeSync(file.fd);
}
// 第三步:验证创建结果
const predicates = new dataSharePredicates.DataSharePredicates();
predicates.equalTo('uri', assetUri);
const fetchResult = this.helper.getAssets({
fetchColumns: [photoAccessHelper.PhotoKeys.URI],
predicates,
});
const asset = await fetchResult.getFirstObject();
return asset;
} catch (err) {
const error = err as BusinessError;
console.error(`[Create] 创建照片失败: ${error.message}`);
return null;
}
}
/**
* 从沙箱文件保存到相册
* 典型场景:下载的图片保存到相册
*/
async saveToGallery(sandboxPath: string, fileName: string): Promise<string | null> {
try {
// 创建媒体资源
const assetUri = await this.helper.createAsset(
photoAccessHelper.PhotoType.IMAGE,
'jpg',
{ title: fileName }
);
// 将沙箱文件内容复制到媒体库
const sourceFile = fileIo.openSync(sandboxPath, fileIo.OpenMode.READ_ONLY);
const destFile = fileIo.openSync(assetUri, fileIo.OpenMode.WRITE_ONLY);
try {
const bufferSize = 4096;
const buffer = new ArrayBuffer(bufferSize);
let readSize = 0;
while ((readSize = fileIo.readSync(sourceFile.fd, buffer)) > 0) {
fileIo.writeSync(destFile.fd, buffer, { length: readSize });
}
} finally {
fileIo.closeSync(sourceFile);
fileIo.closeSync(destFile);
}
console.info(`[Save] 保存到相册成功: ${assetUri}`);
return assetUri;
} catch (err) {
const error = err as BusinessError;
console.error(`[Save] 保存到相册失败: ${error.message}`);
return null;
}
}
// ========== 删除 ==========
/**
* 删除媒体文件
* ⚠️ 这是不可逆操作,删除后文件无法恢复
*/
async deletePhotoAsset(asset: photoAccessHelper.PhotoAsset): Promise<boolean> {
try {
await this.helper.deleteAssets([asset.uri]);
console.info(`[Delete] 删除成功: ${asset.displayName}`);
return true;
} catch (err) {
const error = err as BusinessError;
console.error(`[Delete] 删除失败: ${error.message}`);
return false;
}
}
/**
* 批量删除媒体文件
*/
async deletePhotoAssets(assets: photoAccessHelper.PhotoAsset[]): Promise<number> {
const uris = assets.map(asset => asset.uri);
try {
await this.helper.deleteAssets(uris);
console.info(`[Delete] 批量删除成功: ${uris.length} 个文件`);
return uris.length;
} catch (err) {
const error = err as BusinessError;
console.error(`[Delete] 批量删除失败: ${error.message}`);
return 0;
}
}
// ========== 修改 ==========
/**
* 更新媒体文件的属性
* 支持修改:标题、位置信息、收藏状态等
*/
async updatePhotoAsset(
asset: photoAccessHelper.PhotoAsset,
updates: Record<string, string | number | boolean>
): Promise<boolean> {
try {
// 逐个设置需要更新的属性
for (const [key, value] of Object.entries(updates)) {
switch (key) {
case 'title':
asset.set('title', value as string);
break;
case 'isFavorite':
asset.set('is_favorite', value as boolean);
break;
case 'longitude':
asset.set('longitude', value as number);
break;
case 'latitude':
asset.set('latitude', value as number);
break;
default:
console.warn(`[Update] 不支持的属性: ${key}`);
}
}
// 提交修改
await this.helper.updateAsset(asset);
console.info(`[Update] 更新成功: ${asset.displayName}`);
return true;
} catch (err) {
const error = err as BusinessError;
console.error(`[Update] 更新失败: ${error.message}`);
return false;
}
}
/**
* 收藏/取消收藏照片
*/
async toggleFavorite(asset: photoAccessHelper.PhotoAsset): Promise<boolean> {
const isFavorite = asset.get('is_favorite') as boolean;
return await this.updatePhotoAsset(asset, { isFavorite: !isFavorite });
}
// ========== 用户相册管理 ==========
/**
* 创建用户相册
*/
async createUserAlbum(albumName: string): Promise<photoAccessHelper.Album | null> {
try {
const album = await this.helper.createAlbum(albumName);
console.info(`[Album] 创建相册成功: ${albumName}`);
return album;
} catch (err) {
const error = err as BusinessError;
console.error(`[Album] 创建相册失败: ${error.message}`);
return null;
}
}
/**
* 将照片添加到用户相册
*/
async addPhotoToAlbum(
album: photoAccessHelper.Album,
assets: photoAccessHelper.PhotoAsset[]
): Promise<boolean> {
try {
await album.addAssets(assets);
console.info(`[Album] 添加 ${assets.length} 张照片到相册 "${album.albumName}"`);
return true;
} catch (err) {
const error = err as BusinessError;
console.error(`[Album] 添加到相册失败: ${error.message}`);
return false;
}
}
/**
* 从用户相册移除照片(不会删除原文件)
*/
async removePhotoFromAlbum(
album: photoAccessHelper.Album,
assets: photoAccessHelper.PhotoAsset[]
): Promise<boolean> {
try {
await album.removeAssets(assets);
console.info(`[Album] 从相册 "${album.albumName}" 移除 ${assets.length} 张照片`);
return true;
} catch (err) {
const error = err as BusinessError;
console.error(`[Album] 从相册移除失败: ${error.message}`);
return false;
}
}
/**
* 删除用户相册
*/
async deleteUserAlbum(album: photoAccessHelper.Album): Promise<boolean> {
try {
await this.helper.deleteAlbums([album]);
console.info(`[Album] 删除相册成功: ${album.albumName}`);
return true;
} catch (err) {
const error = err as BusinessError;
console.error(`[Album] 删除相册失败: ${error.message}`);
return false;
}
}
}
四、踩坑与注意事项
4.1 权限必须先申请再使用
这是最常见的坑。photoAccessHelper 的几乎所有操作都需要权限,而且 WRITE_IMAGEVIDEO 权限比 READ_IMAGEVIDEO 更严格——用户可能授予读取权限但拒绝写入权限。
// ❌ 错误:没有申请权限就直接使用
const helper = photoAccessHelper.getPhotoAccessHelper(context);
const albums = await helper.getAlbums(...); // 直接崩溃!
// ✅ 正确:先申请权限
const granted = await permissionManager.ensurePermissions(context);
if (!granted) {
// 引导用户去设置中开启权限
return;
}
const helper = photoAccessHelper.getPhotoAccessHelper(context);
4.2 FetchResult 必须正确遍历
FetchResult 不是数组,它是一个迭代器。必须使用 getFirstObject() + getNextObject() 的方式遍历,不能直接用 for...of。
// ❌ 错误:FetchResult 不是数组
const result = helper.getAssets(fetchOptions);
for (const item of result) { ... } // 编译错误!
// ✅ 正确:使用迭代器模式
const result = helper.getAssets(fetchOptions);
const items: photoAccessHelper.PhotoAsset[] = [];
try {
const first = await result.getFirstObject();
items.push(first);
} catch (err) {
// 没有数据
return items;
}
while (true) {
try {
const next = await result.getNextObject();
items.push(next);
} catch (err) {
break; // 遍历结束
}
}
4.3 创建资源后必须写入文件
createAsset() 只是在媒体库中注册了一个空壳资源,你必须手动写入文件内容。如果只创建不写入,媒体库中会出现一个 0 字节的损坏文件。
// ❌ 错误:只创建不写入
const uri = await helper.createAsset(photoAccessHelper.PhotoType.IMAGE, 'jpg', options);
// 忘记写入文件内容,导致出现 0 字节损坏文件
// ✅ 正确:创建后立即写入
const uri = await helper.createAsset(photoAccessHelper.PhotoType.IMAGE, 'jpg', options);
const file = fileIo.openSync(uri, fileIo.OpenMode.WRITE_ONLY);
try {
// 写入图片数据
fileIo.writeSync(file.fd, imageBuffer);
} finally {
fileIo.closeSync(file.fd);
}
4.4 删除操作不可逆
deleteAssets() 是物理删除,不是移到回收站。删除后文件无法恢复。建议在删除前弹出确认对话框。
// 安全删除:先确认再删除
async function safeDeleteAsset(
helper: photoAccessHelper.PhotoAccessHelper,
asset: photoAccessHelper.PhotoAsset,
confirmed: boolean
): Promise<boolean> {
if (!confirmed) {
console.warn('[Delete] 用户未确认,取消删除');
return false;
}
try {
await helper.deleteAssets([asset.uri]);
return true;
} catch (err) {
return false;
}
}
4.5 URI 的有效期
通过 PhotoAsset.uri 获取的 URI 不是永久有效的。在以下情况下 URI 可能失效:
- 设备重启后
- 媒体库重新扫描后
- 文件被其他应用删除后
建议在需要使用 URI 时实时查询,不要长期缓存。
// ❌ 错误:长期缓存 URI
const cachedUri = asset.uri;
// ... 几天后 ...
const file = fileIo.openSync(cachedUri); // 可能已失效!
// ✅ 正确:实时查询
const assets = await queryManager.queryPhotosWithFilter({ type: 'IMAGE' });
const currentUri = assets[0].uri; // 实时获取
4.6 fetchColumns 的性能影响
fetchColumns 决定了查询返回哪些字段。查询的字段越多,耗时越长。建议只查询需要的字段。
// ❌ 错误:查询所有字段(性能差)
const fetchOptions: photoAccessHelper.FetchOptions = {
fetchColumns: [], // 空数组 = 查询所有字段
predicates: new dataSharePredicates.DataSharePredicates(),
};
// ✅ 正确:只查询需要的字段
const fetchOptions: photoAccessHelper.FetchOptions = {
fetchColumns: [
photoAccessHelper.PhotoKeys.URI,
photoAccessHelper.PhotoKeys.DISPLAY_NAME,
],
predicates: new dataSharePredicates.DataSharePredicates(),
};
五、HarmonyOS 6 适配
5.1 API 变更
| 变更项 | HarmonyOS 5 | HarmonyOS 6 |
|---|---|---|
| 创建资源 | createAsset() |
新增 createAssetWithUri() 支持指定保存路径 |
| 安全访问 | 无 | 新增「安全相册」API,支持隐私照片加密存储 |
| 批量操作 | 逐个操作 | 新增 batchUpdateAssets() 批量更新 |
| 云端资源 | 仅本地 | 新增云端媒体资源查询与下载 |
| 变更通知 | 无 | 新增 registerChange() 监听媒体库变化 |
5.2 媒体库变化监听
HarmonyOS 6 新增了媒体库变化监听能力,可以实时感知照片的增删改:
// HarmonyOS 6 新增:监听媒体库变化
helper.registerChange(
assetUri, // 监听指定资源,传空字符串则监听所有变化
false, // 是否递归监听子目录
(changeData: photoAccessHelper.ChangeData) => {
switch (changeData.type) {
case photoAccessHelper.NotifyType.INSERT:
console.info('[Change] 新增媒体文件');
break;
case photoAccessHelper.NotifyType.DELETE:
console.info('[Change] 删除媒体文件');
break;
case photoAccessHelper.NotifyType.UPDATE:
console.info('[Change] 更新媒体文件');
break;
}
}
);
// 取消监听
helper.unregisterChange(assetUri);
5.3 安全相册
HarmonyOS 6 引入了「安全相册」概念,用户可以将私密照片存入加密相册:
// HarmonyOS 6 新增:创建安全相册
const secureAlbum = await helper.createSecureAlbum('私密相册', userPassword);
// 将照片移入安全相册
await secureAlbum.addAssets(assets);
// 访问安全相册需要验证密码
const albums = await helper.getSecureAlbums(userPassword);
5.4 迁移要点
createAsset()改为createAssetWithUri()以获得更灵活的保存路径控制- 使用
registerChange()替代轮询查询,实时感知媒体库变化 - 安全相册功能需要声明
ohos.permission.SECURE_ALBUM权限 - 云端资源操作需要网络权限和华为账号授权
六、总结
| 知识点 | 核心内容 |
|---|---|
| photoAccessHelper | 媒体库的访问入口,通过 getPhotoAccessHelper(context) 获取 |
| 权限体系 | READ_IMAGEVIDEO(读)+ WRITE_IMAGEVIDEO(写),均为 user_granted |
| 相册管理 | getAlbums() 查询相册、createAlbum() 创建相册、deleteAlbums() 删除相册 |
| 媒体查询 | getAssets() + FetchOptions + DataSharePredicates 条件筛选 |
| FetchResult | 迭代器模式,getFirstObject() + getNextObject() 遍历 |
| 创建资源 | createAsset() 注册空壳 → openSync() 打开文件 → writeSync() 写入数据 |
| 删除资源 | deleteAssets() 物理删除,不可逆,建议删除前确认 |
| 修改资源 | asset.set() 设置属性 → helper.updateAsset() 提交修改 |
| URI 有效期 | URI 不是永久有效,建议实时查询而非长期缓存 |
| HarmonyOS 6 | 新增安全相册、变化监听、批量操作、云端资源等能力 |
💡 一句话总结:photoAccessHelper 是媒体库的「合法通道」,权限申请是前提,FetchResult 遍历是基本功,创建资源后必须写入文件内容,删除操作不可逆需谨慎,URI 不要长期缓存。掌握这些,你就能安全高效地操作用户的照片和视频了。
- 点赞
- 收藏
- 关注作者
评论(0)