HarmonyOS鸿蒙Next中应用内相册功能如何实现?
HarmonyOS鸿蒙Next中应用内相册功能如何实现? 我们正在移植android应用到鸿蒙,目前有个应用内相册功能无法完整实现。
安卓的实现方案是: 通过MediaStore 来查询应用自己保存到相册的图片 来实现。 即使应用卸载了,再次安装。由于这些图片是应用自己之前保存的 也是可以再次查询到的。
鸿蒙的实现方案是: 我们鸿蒙的实现方案是沙盒保存一份。
应用文件访问
https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/app-file-access
再通过
photoAccessHelper.getPhotoAccessHelper(context);
弹出系统弹窗将沙盒的图片保存到系统相册。
现在有个问题,如果用户卸载app,再重新安装,我们保存的沙盒的图片就没了。然后用户打开应用内相册模块,一张图片都没有。去系统相册看,又有图片,就很难受。
尝试方案: PhotoViewPicker 这是选图片的场景 我们是浏览相册的场景。 还有这个 ohos.permission.READ_IMAGEVIDEO 申请相册管理模块功能相关权限 也尝试过 我们的场景不符合使用权限。
求助:有没有相册这一块的功能,比如跳转到指定相册供用户浏览。或者在app内嵌入一个指定的系统相册的浏览界面。或者有什么api能读取应用自己保存到相册的图片?或者能跳转到指定的系统相册也行啊?
总结就是:如何在应用内浏览系统相册?
更多关于HarmonyOS鸿蒙Next中应用内相册功能如何实现?的实战教程也可以访问 https://www.itying.com/category-93-b0.html
开发者你好,
可以尝试通过AlbumPickerComponent组件搭配PhotoPickerComponent组件实现在无需申请受限权限的情况下,在应用中浏览特定应用保存的图片和视频。 AlbumPickerComponent用于浏览用户相册列表,可以在相册列表中选择本应用保存的图片和视频,用户选择后可以获取相册URI。 PhotoPickerComponent用于浏览和获取系统图库的图片和视频资源,可以通过PickerController的setData接口设置相册URI(DataType选择SET_ALBUM_URI),来浏览特定相册的图片和视频。 具体可参考官网指南:使用PhotoPicker组件访问图片/视频和使用AlbumPicker组件访问相册列表。
如有问题,请及时反馈。
更多关于HarmonyOS鸿蒙Next中应用内相册功能如何实现?的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html
可以尝试通过图片名称过滤系统相册内图片:
背景知识
- PhotoAccessHelper:相册管理模块,提供getAlbums根据检索选项和相册类型获取相册,相册类型分为系统相册和用户相册。
- AbsAlbum:实体相册,通过PhotoAccessHelper获取到实体相册后,可以通过getAssets获取相册中的文件实例PhotoAsset。
解决方案
申请相册读取权限ohos.permission.READ_IMAGEVIDEO,具体参考申请相册管理模块权限;然后根据dataSharePredicates谓词查询,通过调用getAlbums接口获取全部相册图片对象PhotoAsset,根据displayName筛选。
完整示例参考如下:
import { abilityAccessCtrl, common, Permissions } from '@kit.AbilityKit';
import { BusinessError } from '@ohos.base';
import { dataSharePredicates } from '@kit.ArkData';
import { photoAccessHelper } from '@kit.MediaLibraryKit';
@Entry
@Component
struct Index {
private context = this.getUIContext().getHostContext() as Context as common.UIAbilityContext;
aboutToAppear(): void {
// 申请相册读取权限
let atManager = abilityAccessCtrl.createAtManager();
// requestPermissionsFromUser会判断权限的授权状态来决定是否唤起弹窗
const permissions: Array<Permissions> = ['ohos.permission.READ_MEDIA', 'ohos.permission.WRITE_MEDIA'];
atManager.requestPermissionsFromUser(this.context, permissions).then(async (data) => { //需要用户允许授权图库权限
let grantStatus: Array<number> = data.authResults;
let length: number = grantStatus.length;
for (let i = 0; i < length; i++) {
if (grantStatus[i] === 0) { // 用户同意权限之后进行的操作
// ...
} else {
// 用户拒绝授权,提示用户必须授权才能访问当前页面的功能,并引导用户到系统设置中打开相应的权限
return;
}
}
}).catch((err: BusinessError) => {
console.error(`requestPermissionsFromUser failed, code is ${err.code}, message is ${err.message}`);
});
}
// 筛选相册图片
async selectedPhoto() {
let phAccessHelper = photoAccessHelper.getPhotoAccessHelper(this.context);
let predicates: dataSharePredicates.DataSharePredicates = new dataSharePredicates.DataSharePredicates();
const photoTitle = photoAccessHelper.PhotoKeys.TITLE;
let fetchOptions: photoAccessHelper.FetchOptions = {
fetchColumns: [photoTitle],
predicates: predicates
};
try {
// 获取系统相册中的图片相册
let albumFetchResult: photoAccessHelper.FetchResult<photoAccessHelper.Album> =
await phAccessHelper.getAlbums(photoAccessHelper.AlbumType.SYSTEM, photoAccessHelper.AlbumSubtype.IMAGE);
let album: photoAccessHelper.Album = await albumFetchResult.getFirstObject();
let photoFetchResult: photoAccessHelper.FetchResult<photoAccessHelper.PhotoAsset> =
await album.getAssets(fetchOptions);
// 获取相册全部图片资源
let photoAssets = await photoFetchResult.getAllObjects();
for (let i = 0; i < photoAssets.length; i++) {
let photoAsset: photoAccessHelper.PhotoAsset = photoAssets[i];
console.info('photo album getAssets successfully', `photoAsset displayName: (${photoAsset.displayName})`);
}
// 根据名称过滤
let showPhotos = photoAssets.filter((photoAsset: photoAccessHelper.PhotoAsset) => {
const name = photoAsset.displayName;
// 根据名称中包含的字符串过滤,这里是过滤png的图片
return name.substring(name.indexOf('.') + 1) == 'png';
});
for (let i = 0; i < showPhotos.length; i++) {
let photoAsset: photoAccessHelper.PhotoAsset = showPhotos[i];
console.info('showPhotos successfully', `showPhotos: (${photoAsset.displayName})`);
}
photoFetchResult.close();
albumFetchResult.close();
} catch (err) {
console.error('photo failed with err: ' + err);
}
}
build() {
Column() {
Button('selected photos')
.onClick(() => {
this.selectedPhoto();
})
}
.width('100%')
.height('100%')
.alignItems(HorizontalAlign.Center)
}
}
常见FAQ
Q:保存图片至系统图片时,如何重命名图片名称?
A:可以使用createAsset创建图片资源写入图库,其中可以重命名图片标题。
Q:使用photoAccessHelper,如何只显示jpg或者png的图片供用户选择。
A:获取图片PhotoAsset并根据displayName过滤,代码参考解决方案中的步骤二。
老铁啊 ohos.permission.READ_IMAGEVIDEO 这玩意不开放啊。我们普通应用没法申请啊。这个权限非必要不开放的。,
我感觉应该是这几点
- 根本原因:鸿蒙应用数据默认存放在沙盒中,卸载应用会清空沙盒数据。
- 问题所在:您通过 photoAccessHelper 将图片保存到了系统相册,但应用本身无法直接访问这些图片。
- 解决方案:
- 推荐方案:申请 ohos.permission.READ_IMAGEVIDEO 权限,让应用能直接读取系统相册中的图片。
- 备选方案:使用分布式文件系统(如网盘)同步图片,避免因卸载导致数据丢失。
尊敬的开发者,您好!您的问题已受理,请您耐心等待,感谢您的理解与支持!
你现在的核心需求是:在鸿蒙应用内实现和安卓 MediaStore 类似的效果 —— 既能浏览应用自身之前保存到系统相册的图片(即使应用卸载重装后也能查询到),同时解决当前沙盒文件卸载丢失、系统相册有图但应用内无图的问题,本质是实现应用内浏览系统相册(优先筛选自身应用保存的图片)。
首先先明确鸿蒙的核心差异:鸿蒙的沙盒文件(应用私有目录)随应用卸载删除,而系统相册的图片存储在公共媒体目录,是持久化的(不会随应用卸载丢失),你的核心问题是没有正确读取系统相册中的持久化图片,而是依赖了沙盒内的副本。
下面提供两种可行方案,从简单到复杂,优先推荐方案一(符合你的需求且开发成本低):
方案一:核心解决方案 —— 通过 PhotoAccessHelper 读取系统相册(持久化图片)
这是鸿蒙官方推荐的读取系统公共媒体文件(图片 / 视频)的 API,对应安卓的 MediaStore,能够读取到你之前通过 PhotoAccessHelper 保存到系统相册的图片(即使应用卸载重装,只要系统相册的图片没被用户删除,就能查询到),完全解决你的核心问题。
步骤 1:配置权限(必须)
和安卓一样,读取系统相册需要申请权限,且鸿蒙分为普通权限和危险权限,读取图片视频属于危险权限,需要两步配置:
-
在
module.json5中声明权限打开应用的src/main/module.json5文件,在abilities同级添加requestPermissions节点:{ "module": { // 其他配置... "requestPermissions": [ { "name": "ohos.permission.READ_IMAGEVIDEO", // 读取图片/视频权限 "reason": "需要访问系统相册以显示应用保存的图片", "usedScene": { "abilities": [ "your.package.name.MainAbility" // 替换为你的主Ability全类名 ], "when": "always" } } ] } } -
在代码中动态申请危险权限鸿蒙的危险权限不能仅靠清单声明,必须在运行时动态申请(用户授权后才能使用),建议在应用启动页或相册页面初始化时申请:
import ohos.aafwk.ability.Ability; import ohos.agp.window.dialog.ToastDialog; import ohos.security.SystemPermission; import ohos.security.permission.PermissionManager; import ohos.security.permission.PermissionRequestResult; public class AlbumAbility extends Ability { // 权限请求码,自定义即可 private static final int PERMISSION_REQUEST_CODE = 1001; @Override public void onStart(Intent intent) { super.onStart(intent); // 检查并申请读取图片/视频权限 checkAndRequestPermission(); } // 检查权限并动态申请 private void checkAndRequestPermission() { String permission = SystemPermission.READ_IMAGEVIDEO; // 检查权限是否已授权 int permissionStatus = PermissionManager.checkPermission(this, permission); if (permissionStatus == PermissionManager.PERMISSION_GRANTED) { // 权限已授权,直接查询系统相册图片 querySystemAlbumPhotos(); } else { // 权限未授权,动态申请 PermissionManager.requestPermissions(this, new String[]{permission}, PERMISSION_REQUEST_CODE, new PermissionRequestResult() { @Override public void onResult(int requestCode, String[] permissions, int[] grantResults) { if (requestCode == PERMISSION_REQUEST_CODE && grantResults.length > 0) { if (grantResults[0] == PermissionManager.PERMISSION_GRANTED) { // 用户授权成功,查询系统相册图片 querySystemAlbumPhotos(); } else { // 用户拒绝授权,提示无法访问相册 new ToastDialog(AlbumAbility.this) .setText("未授权访问相册,无法显示应用保存的图片") .show(); } } } }); } } }
步骤 2:通过 PhotoAccessHelper 查询系统相册中的图片
PhotoAccessHelper 不仅能保存图片到系统相册,更能查询 / 读取系统相册中的所有图片(包括你应用之前保存的),核心是通过 query() 方法查询媒体数据库,和安卓的 ContentResolver.query() 类似。
核心代码实现(查询 + 读取图片)
import ohos.app.Context;
import ohos.media.image.ImageSource;
import ohos.media.image.PixelMap;
import ohos.media.photo.PhotoAccessHelper;
import ohos.media.photo.PhotoColumn;
import ohos.utils.net.Uri;
import ohos.data.resultset.ResultSet;
import java.util.ArrayList;
import java.util.List;
// 定义图片实体类,存储图片信息
class AlbumPhoto {
private String id; // 图片唯一标识
private Uri uri; // 图片Uri(用于读取图片内容)
private String displayName; // 图片文件名
private long size; // 图片大小
private long dateAdded; // 图片添加时间
// 构造方法、getter/setter 省略...
}
// 查询系统相册图片的核心方法
private List<AlbumPhoto> querySystemAlbumPhotos() {
List<AlbumPhoto> photoList = new ArrayList<>();
Context context = this; // 此处为Ability,可直接作为Context
try {
// 1. 获取 PhotoAccessHelper 实例
PhotoAccessHelper photoHelper = PhotoAccessHelper.getPhotoAccessHelper(context);
// 2. 定义需要查询的列(和安卓 MediaStore 的列类似)
String[] columns = {
PhotoColumn.ID, // 图片唯一ID
PhotoColumn.URI, // 图片Uri(关键,用于后续读取图片)
PhotoColumn.DISPLAY_NAME, // 文件名
PhotoColumn.SIZE, // 文件大小
PhotoColumn.DATE_ADDED // 添加时间
};
// 3. 构建查询条件(可选:优先筛选你应用保存的图片)
// 如果你保存图片时自定义了文件名前缀/后缀,可通过 DISPLAY_NAME 筛选
// 例如:你的应用图片都以 "MyApp_" 开头,查询条件可写为 PhotoColumn.DISPLAY_NAME + " LIKE 'MyApp_%'"
String selection = null; // 为 null 时查询所有图片
String[] selectionArgs = null;
// 4. 定义排序方式(按添加时间倒序,最新的图片在前面)
String sortOrder = PhotoColumn.DATE_ADDED + " DESC";
// 5. 执行查询,获取结果集
ResultSet resultSet = photoHelper.query(
PhotoAccessHelper.PHOTO_URI, // 查询图片(视频为 VIDEO_URI)
columns,
selection,
selectionArgs,
sortOrder
);
// 6. 解析结果集,封装为图片实体列表
if (resultSet != null && resultSet.goToFirstRow()) {
do {
AlbumPhoto photo = new AlbumPhoto();
// 读取查询结果的对应列(通过列名获取索引)
photo.setId(resultSet.getString(resultSet.getColumnIndex(PhotoColumn.ID)));
photo.setUri(Uri.parse(resultSet.getString(resultSet.getColumnIndex(PhotoColumn.URI))));
photo.setDisplayName(resultSet.getString(resultSet.getColumnIndex(PhotoColumn.DISPLAY_NAME)));
photo.setSize(resultSet.getLong(resultSet.getColumnIndex(PhotoColumn.SIZE)));
photo.setDateAdded(resultSet.getLong(resultSet.getColumnIndex(PhotoColumn.DATE_ADDED)));
photoList.add(photo);
} while (resultSet.goToNextRow()); // 遍历所有结果行
}
// 7. 关闭结果集,释放资源
if (resultSet != null) {
resultSet.close();
}
} catch (Exception e) {
e.printStackTrace();
new ToastDialog(context).setText("查询相册图片失败:" + e.getMessage()).show();
}
// 8. 后续:将 photoList 绑定到你的相册列表控件(如 ListView、RecyclerView)进行展示
showAlbumPhotos(photoList);
return photoList;
}
// 读取图片 Uri 对应的 PixelMap(用于界面展示)
private PixelMap getPixelMapFromUri(Context context, Uri uri) {
try {
// 通过 PhotoAccessHelper 打开 Uri 对应的图片流
PhotoAccessHelper photoHelper = PhotoAccessHelper.getPhotoAccessHelper(context);
ImageSource imageSource = ImageSource.create(photoHelper.openAssetFile(uri, "r"), null);
// 构建图片读取选项(可自定义尺寸,避免大图内存溢出)
ImageSource.DecodingOptions options = new ImageSource.DecodingOptions();
options.desiredSize = new ohos.media.image.Size(400, 400); // 缩放到 400x400 展示
return imageSource.createPixelMap(options);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
// 展示相册图片(绑定到列表控件,此处为伪代码,需替换为你的实际UI逻辑)
private void showAlbumPhotos(List<AlbumPhoto> photoList) {
// 示例:遍历图片列表,获取 PixelMap 并设置到 Image 组件
// for (AlbumPhoto photo : photoList) {
// PixelMap pixelMap = getPixelMapFromUri(this, photo.getUri());
// imageComponent.setPixelMap(pixelMap);
// }
}
关键说明
- 持久化保障:通过
PhotoAccessHelper查询的是系统公共媒体目录的图片,不是应用沙盒内的文件,因此即使应用卸载重装,只要用户没删除系统相册中的图片,就能查询到(和安卓MediaStore效果一致)。 - 筛选应用自身图片:如果想只显示你应用保存的图片(而不是系统所有图片),有两种可靠方式:
- 文件名约定:保存图片时,给文件名加上固定前缀(如
MyApp_20240520_100000.jpg),查询时通过selection条件筛选(PhotoColumn.DISPLAY_NAME + " LIKE 'MyApp_%'")。 - 保存时写入自定义属性:鸿蒙
PhotoAccessHelper支持保存图片时写入自定义Exif信息,查询时通过Exif信息筛选(适合更灵活的场景,开发成本稍高)。
- 文件名约定:保存图片时,给文件名加上固定前缀(如
- 沙盒副本可移除:实现该方案后,你可以不再需要在沙盒中保存副本,直接读取系统相册的图片即可,既节省存储空间,又解决了卸载丢失的问题。
方案二:跳转至系统指定相册(备选方案)
如果你的需求是直接跳转到系统相册的指定分类(如你应用保存的图片相册),而不是在应用内嵌入浏览界面,鸿蒙支持通过隐式意图跳转系统相册应用,核心是使用 Intent 调用系统相册的能力。
核心代码
import ohos.aafwk.content.Intent;
import ohos.aafwk.content.Operation;
import ohos.utils.Uri;
// 跳转系统相册(优先打开图片分类)
private void jumpToSystemAlbum() {
Intent intent = new Intent();
Operation operation = new Intent.OperationBuilder()
.withAction("ohos.intent.action.VIEW_MEDIA") // 系统媒体查看动作
.withUri(Uri.parse("content://media/external/images/media")) // 系统图片目录Uri
.build();
intent.setOperation(operation);
// 启动系统相册应用
if (this.canStartAbility(intent)) {
this.startAbility(intent);
} else {
new ToastDialog(this).setText("设备不支持跳转系统相册").show();
}
}
说明
- 该方案无需自己实现相册浏览界面,直接复用系统相册,开发成本极低。
- 局限性:无法精准跳转到 “你应用专属” 的相册(鸿蒙系统相册的分类由系统管理,第三方应用无法直接指定自定义相册),只能跳转到系统图片总目录或默认分类。
- 适用场景:如果应用内相册的核心需求是让用户查看图片,而非复杂的编辑 / 操作,可作为备选方案。
方案对比与选型建议
| 方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
方案一:PhotoAccessHelper 应用内浏览 |
1. 完全符合安卓 MediaStore 效果,持久化无丢失;2. 可在应用内自定义界面风格,用户体验统一;3. 可精准筛选应用自身保存的图片。 |
1. 需要自己实现相册列表、图片预览等 UI;2. 需要处理图片加载、内存优化等细节。 | 核心需求是应用内嵌入相册浏览,要求体验统一、数据持久化(推荐你的项目使用)。 |
| 方案二:跳转系统相册 | 1. 开发成本极低,无需实现 UI;2. 无需处理图片加载、内存优化等问题。 | 1. 无法精准跳转到应用专属相册;2. 离开应用自身,用户体验不统一;3. 无法自定义相册功能(如编辑、批量操作)。 | 核心需求是快速让用户查看图片,对界面风格和功能要求不高。 |
总结
- 核心解决方案是方案一,通过
PhotoAccessHelper读取系统相册的持久化图片,完全解决卸载重装后应用内无图的问题,且和安卓MediaStore功能对齐。 - 实现关键步骤:配置并动态申请
ohos.permission.READ_IMAGEVIDEO权限、通过PhotoAccessHelper.query()查询系统图片、解析ResultSet并展示图片。 - 如需精准筛选应用自身图片,可通过约定文件名前缀或自定义 Exif 信息实现,优先选择文件名前缀(简单高效)。
- 方案二可作为备选,用于快速实现跳转系统相册的需求,但其灵活性和定制化程度较低。
老铁啊 ohos.permission.READ_IMAGEVIDEO 这玩意不开放啊。我们普通应用没法申请啊。这个权限非必要不开放的。
在HarmonyOS Next中,应用内相册功能主要通过以下方式实现:
- 使用媒体库管理模块(
@ohos.file.photoAccessHelper)访问和操作相册媒体文件。 - 通过用户文件访问框架(
@ohos.file.fs)管理应用内的私有媒体文件目录。 - 调用图片处理接口(
@ohos.image)进行图片解码、编辑等操作。 - 利用安全控件(
<Image>)加载和显示图片资源。
关键步骤包括申请相册访问权限、查询媒体文件、创建应用私有相册目录以及使用图片组件展示。
在HarmonyOS Next中,应用内浏览系统相册的核心是使用 PhotoViewPicker 组件。你提到的“浏览相册”场景,正是 PhotoViewPicker 的主要功能之一,它并非仅限于“选择图片”。
关键点澄清与实现方案:
-
PhotoViewPicker的正确使用:你提到“PhotoViewPicker 这是选图片的场景”,这是一个误解。PhotoViewPicker是一个视图(View)组件,其设计目的就是在应用内嵌入一个系统相册的浏览界面。用户在此界面内可以进行浏览、选择(单选或多选)等操作。这正是你需要的“在app内嵌入一个指定的系统相册的浏览界面”。 -
实现应用内相册浏览:
- 核心API:使用
PhotoViewPicker的select方法并配置PhotoSelectOptions。 - 关键配置:将
PhotoSelectOptions中的maxSelectNumber设置为 0。这明确告知系统,本次调用目的仅为浏览,而非选择并返回图片给应用。用户在该视图内可以自由翻看系统相册(包括你的应用之前保存的图片),但无法触发“完成选择”的操作。 - 权限:此方式不需要申请
ohos.permission.READ_IMAGEVIDEO权限。该权限用于直接通过photoAccessHelper查询媒体库数据库,与你当前“嵌入系统UI进行浏览”的场景不同。
- 核心API:使用
代码示例:
import { photoViewPicker } from '@kit.MediaKit';
import { BusinessError } from '@kit.BasicServicesKit';
let photoPicker: photoViewPicker.PhotoViewPicker = new photoViewPicker.PhotoViewPicker();
// 配置选项:最大选择数设为0,表示仅浏览
let options: photoViewPicker.PhotoSelectOptions = {
maxSelectNumber: 0,
MIMEType: photoViewPicker.PhotoViewMIMETypes.IMAGE_TYPE, // 浏览图片类型
};
try {
// 启动应用内的系统相册浏览视图
photoPicker.select(options).then((photoSelectResult: photoViewPicker.PhotoSelectResult) => {
// 当用户按返回键退出浏览时,会进入这里。由于maxSelectNumber为0,photoSelectResult.photoUris为空数组。
console.info('PhotoViewPicker dismissed');
}).catch((err: BusinessError) => {
console.error(`PhotoViewPicker failed with error: ${err.code}, ${err.message}`);
});
} catch (error) {
let err: BusinessError = error as BusinessError;
console.error(`PhotoViewPicker initialization failed with error: ${err.code}, ${err.message}`);
}
关于你原有方案的补充说明:
你现有的“沙盒保存 -> 弹出系统保存”方案,是HarmonyOS中应用将图片持久化共享到系统相册的标准流程。应用卸载后,沙盒数据被清除是系统安全设计,但通过此流程保存到系统相册的图片会永久保留。重新安装后,要展示这些图片,正确的路径就是使用上述的 PhotoViewPicker 浏览模式,让用户直接在应用内查看系统相册中的内容,而不是试图从沙盒或通过直接查询数据库的方式获取。
总结:
要实现在HarmonyOS Next应用内浏览系统相册(包含本应用曾保存的图片),应使用 PhotoViewPicker 组件,并将选择数量 maxSelectNumber 配置为0。这提供了与系统相册一致的浏览体验,且无需处理复杂的媒体库查询权限和路径管理问题。

