ReactNative for Harmony 项目鸿蒙化三方库集成实战:react-native-camera-roll(保存到相册)
是 React Native 社区最流行的相册访问库,提供了一套完整的相册管理解决方案。它支持保存图片/视频到相册、获取相册中的图片/视频、删除图片/视频等多种功能,并且完全兼容 Android、iOS 和 HarmonyOS 三端。库名称版本信息7.8.4: 支持 RN 0.72 版本7.10.1: 支持 RN 0.77 版本官方仓库主要功能保存图片/视频到相册获取相册中的图片/视频获取相册列表

📋 前言
react-native-camera-roll 是 React Native 社区最流行的相册访问库,提供了一套完整的相册管理解决方案。它支持保存图片/视频到相册、获取相册中的图片/视频、删除图片/视频等多种功能,并且完全兼容 Android、iOS 和 HarmonyOS 三端。
🎯 库简介
基本信息
- 库名称: @react-native-ohos/camera-roll
- 版本信息:
7.8.4: 支持 RN 0.72 版本7.10.1: 支持 RN 0.77 版本
- 官方仓库: https://github.com/react-native-oh-library/react-native-cameraroll
- 主要功能:
- 保存图片/视频到相册
- 获取相册中的图片/视频
- 获取相册列表
- 删除图片/视频
- 获取图片缩略图
- 兼容 Android、iOS 和 HarmonyOS
- 兼容性验证:
- RNOH: 0.72.20; SDK: HarmonyOS NEXT Developer Beta1; IDE: DevEco Studio 5.0.3.200; ROM: 3.0.0.18;
- RNOH: 0.77.18; SDK: HarmonyOS 6.0.0 Release SDK; IDE: DevEco Studio 6.0.0.868; ROM: 6.0.0.112;
为什么需要这个库?
- 功能完整: 提供完整的相册管理解决方案
- 跨平台: 在三端提供一致的体验
- 易于使用: API 简单直观
- 性能优异: 原生实现,高效稳定
- 灵活配置: 支持多种配置选项
📦 安装步骤
1. 使用 npm 安装
根据您的 RN 版本选择对应的包名:
npm install @react-native-ohos/camera-roll@7.8.4-rc.1
2. 验证安装
安装完成后,检查 package.json 文件,应该能看到新增的依赖:
{
"dependencies": {
"@react-native-ohos/camera-roll": "^7.8.4-rc.1",
// ... 其他依赖
}
}
🔧 HarmonyOS 平台配置 ⭐
1. 在工程根目录的 oh-package.json5 添加 overrides 字段
首先需要使用 DevEco Studio 打开项目里的 HarmonyOS 工程 harmony
打开 harmony/oh-package.json5,添加以下配置:
{
...
"overrides": {
"@rnoh/react-native-openharmony": "^0.72.90"
}
}
2. 引入原生端代码
方法一:通过 har 包引入(不推荐)
[!TIP] har 包位于三方库安装路径的
harmony文件夹下。
打开 entry/oh-package.json5,添加以下依赖:
"dependencies": {
"@rnoh/react-native-openharmony": "0.72.90",
"@react-native-ohos/camera-roll": "file:../../node_modules/@react-native-ohos/camera-roll/harmony/camera_roll.har"
}
点击右上角的 sync 按钮
或者在终端执行:
cd entry
ohpm install
方法二:直接链接源码
步骤 1: 把 <RN工程>/node_modules/@react-native-ohos/camera-roll/harmony 目录下的源码 camera_roll 复制到 harmony(鸿蒙壳工程)工程根目录下。
步骤 2: 在 harmony 工程根目录的 build-profile.template.json5(若存在)和 build-profile.json5 添加以下模块:
modules: [
...
{
name: 'camera_roll',
srcPath: './camera_roll',
}
]
步骤 3: 打开 camera_roll/oh-package.json5,修改 react-native-openharmony 和项目的版本一致。
步骤 4: 打开 entry/oh-package.json5,添加以下依赖:
"dependencies": {
"@rnoh/react-native-openharmony": "0.72.90",
"@react-native-ohos/camera-roll": "file:../camera_roll"
}
步骤 5: 点击 DevEco Studio 右上角的 sync 按钮
3. 配置 CMakeLists 和引入 CameraRollPackage
[!TIP] 仅 0.77 版本需要配置 CMakeLists 和引入 CameraRollPackage。
打开 entry/src/main/cpp/CMakeLists.txt,添加:
project(rnapp)
cmake_minimum_required(VERSION 3.4.1)
set(CMAKE_SKIP_BUILD_RPATH TRUE)
set(OH_MODULE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../../../oh_modules")
set(NODE_MODULES "${CMAKE_CURRENT_SOURCE_DIR}/../../../../../node_modules")
set(RNOH_APP_DIR "${CMAKE_CURRENT_SOURCE_DIR}")
set(RNOH_CPP_DIR "${OH_MODULE_DIR}/@rnoh/react-native-openharmony/src/main/cpp")
set(CMAKE_ASM_FLAGS "-Wno-error:unused-command-line-argument -Qunused-arguments")
set(CMAKE_CXX_FLAGS "-fstack-protector-strong -Wl,-z,relro,-z,noexecstack -s -fPIE -pie")
add_compile_definitions(WITH_HITRACE_SYSTRACE)
set(WITH_HITRACE_SYSTRACE 1) # for other CMakeLists.txt files to use
add_subdirectory("${RNOH_CPP_DIR}" ./rn)
add_library(rnoh_app SHARED
"${RNOH_CPP_DIR}/RNOHAppNapiBridge.cpp" "./PackageProvider.cpp")
target_link_libraries(rnoh_app PUBLIC rnoh)
set(OH_MODULES "${CMAKE_CURRENT_SOURCE_DIR}/../../../oh_modules")
+ add_subdirectory("${OH_MODULES}/@react-native-ohos/camera-roll/src/main/cpp" ./camera-roll)
+ target_link_libraries(rnoh_app PUBLIC rnoh_camera_roll)
打开 entry/src/main/cpp/PackageProvider.cpp,添加:
#include "RNOH/PackageProvider.h"
#include "generated/RNOHGeneratedPackage.h"
#include "SamplePackage.h"
+ #include "CameraRollPackage.h"
using namespace rnoh;
std::vector<std::shared_ptr<Package>> PackageProvider::getPackages(Package::Context ctx) {
return {
std::make_shared<RNOHGeneratedPackage>(ctx),
std::make_shared<SamplePackage>(ctx),
+ std::make_shared<CameraRollPackage>(ctx)
};
}
4. 在 ArkTs 侧引入 CameraRollPackage
打开 entry/src/main/ets/RNPackagesFactory.ts,添加:
+ import { CameraRollPackage } from '@react-native-ohos/camera-roll/ts';
export function createRNPackages(ctx: RNPackageContext): RNPackage[] {
return [
new SamplePackage(ctx),
+ new CameraRollPackage(ctx)
];
}
5. 运行
点击右上角的 sync 按钮
或者在终端执行:
cd entry
ohpm install
然后编译、运行即可。
💻 完整代码示例
下面是一个完整的示例,展示了 react-native-camera-roll 的各种使用场景:
import React, { useState } from 'react';
import {
View,
Text,
StyleSheet,
TouchableOpacity,
Image,
ScrollView,
Alert,
TextInput,
} from 'react-native';
import {
CameraRoll,
type PhotoIdentifier,
type GroupTypes,
} from '@react-native-camera-roll/camera-roll';
function CameraRollDemo() {
const [savedImages, setSavedImages] = useState<string[]>([]);
const [imageUrl, setImageUrl] = useState('');
const [lastSavedAsset, setLastSavedAsset] = useState<PhotoIdentifier | null>(null);
// 保存图片到相册
const saveToCameraRoll = async () => {
if (!imageUrl.trim()) {
Alert.alert('提示', '请输入图片URL');
return;
}
try {
const asset = await CameraRoll.saveAsset(imageUrl);
setLastSavedAsset(asset);
setSavedImages([...savedImages, asset.node.image.uri]);
Alert.alert('成功', '图片已保存到相册');
} catch (error: any) {
Alert.alert('错误', error.message || '保存失败');
}
};
// 使用默认图片URL
const useDefaultImage = () => {
setImageUrl('https://res.vmallres.com/uomcdn/CN/cms/202408/5442d69d916d4bcf9ee740d595a164fb.jpg');
};
// 清空保存记录
const clearHistory = () => {
setSavedImages([]);
setLastSavedAsset(null);
};
return (
<ScrollView style={styles.container}>
<View style={styles.content}>
<Text style={styles.title}>相册管理器</Text>
{/* 保存图片区域 */}
<View style={styles.section}>
<Text style={styles.sectionTitle}>保存图片到相册</Text>
<TextInput
style={styles.input}
placeholder="输入图片URL"
value={imageUrl}
onChangeText={setImageUrl}
/>
<TouchableOpacity
style={styles.linkButton}
onPress={useDefaultImage}
>
<Text style={styles.linkText}>使用示例图片</Text>
</TouchableOpacity>
<TouchableOpacity
style={styles.primaryButton}
onPress={saveToCameraRoll}
>
<Text style={styles.buttonText}>保存到相册</Text>
</TouchableOpacity>
</View>
{/* 最后保存的图片信息 */}
{lastSavedAsset && (
<View style={styles.section}>
<Text style={styles.sectionTitle}>最后保存的图片</Text>
<Image
source={{ uri: lastSavedAsset.node.image.uri }}
style={styles.image}
/>
<View style={styles.infoRow}>
<Text style={styles.infoLabel}>文件名:</Text>
<Text style={styles.infoText}>
{lastSavedAsset.node.image.filename}
</Text>
</View>
<View style={styles.infoRow}>
<Text style={styles.infoLabel}>尺寸:</Text>
<Text style={styles.infoText}>
{lastSavedAsset.node.image.width} x {lastSavedAsset.node.image.height}
</Text>
</View>
<View style={styles.infoRow}>
<Text style={styles.infoLabel}>文件大小:</Text>
<Text style={styles.infoText}>
{lastSavedAsset.node.image.fileSize ? `${(lastSavedAsset.node.image.fileSize / 1024).toFixed(2)} KB` : '未知'}
</Text>
</View>
<View style={styles.infoRow}>
<Text style={styles.infoLabel}>时间戳:</Text>
<Text style={styles.infoText}>
{new Date(lastSavedAsset.node.timestamp).toLocaleString()}
</Text>
</View>
<View style={styles.infoRow}>
<Text style={styles.infoLabel}>URI:</Text>
<Text style={styles.infoText} numberOfLines={2}>
{lastSavedAsset.node.image.uri}
</Text>
</View>
</View>
)}
{/* 保存历史 */}
{savedImages.length > 0 && (
<View style={styles.section}>
<View style={styles.sectionHeader}>
<Text style={styles.sectionTitle}>保存历史 ({savedImages.length})</Text>
<TouchableOpacity
style={styles.smallButton}
onPress={clearHistory}
>
<Text style={styles.smallButtonText}>清空</Text>
</TouchableOpacity>
</View>
<View style={styles.imageGrid}>
{savedImages.map((uri, index) => (
<Image
key={index}
source={{ uri }}
style={styles.gridImage}
/>
))}
</View>
</View>
)}
{/* 说明 */}
<View style={styles.noteSection}>
<Text style={styles.noteTitle}>功能说明:</Text>
<Text style={styles.noteText}>
• 支持的图片格式:png/jpg/jpeg/heif/bmp/gif/webp/svg/heic
</Text>
<Text style={styles.noteText}>
• 支持的视频格式:mp4/mov
</Text>
<Text style={styles.noteText}>
• 需要在应用权限中申请相册读写权限
</Text>
<Text style={styles.noteText}>
• HarmonyOS 部分接口受限,需要申请受限权限
</Text>
</View>
{/* 使用限制 */}
<View style={styles.warningSection}>
<Text style={styles.warningTitle}>使用限制:</Text>
<Text style={styles.warningText}>
• getPhotos、getAlbums 等接口受限,需要申请受限权限
</Text>
<Text style={styles.warningText}>
• deletePhotos 功能暂未适配 HarmonyOS
</Text>
<Text style={styles.warningText}>
• 部分接口因 HarmonyOS 安全策略无法使用
</Text>
<Text style={styles.warningText}>
• saveAsset 接口未完全支持所有格式
</Text>
</View>
</View>
</ScrollView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#f5f5f5',
},
content: {
padding: 20,
},
title: {
fontSize: 24,
fontWeight: 'bold',
marginBottom: 20,
textAlign: 'center',
color: '#333',
},
section: {
marginBottom: 20,
padding: 15,
backgroundColor: '#fff',
borderRadius: 10,
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.1,
shadowRadius: 4,
elevation: 3,
},
sectionTitle: {
fontSize: 16,
fontWeight: 'bold',
marginBottom: 10,
color: '#333',
},
sectionHeader: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: 10,
},
input: {
height: 50,
borderWidth: 1,
borderColor: '#ddd',
borderRadius: 8,
paddingHorizontal: 15,
marginBottom: 10,
backgroundColor: '#f9f9f9',
fontSize: 14,
},
linkButton: {
marginBottom: 10,
},
linkText: {
color: '#42a5f5',
fontSize: 14,
textDecorationLine: 'underline',
},
primaryButton: {
backgroundColor: '#42a5f5',
paddingVertical: 15,
borderRadius: 8,
alignItems: 'center',
},
buttonText: {
color: '#fff',
fontSize: 16,
fontWeight: 'bold',
},
image: {
width: '100%',
height: 200,
resizeMode: 'contain',
marginBottom: 10,
borderRadius: 8,
},
infoRow: {
flexDirection: 'row',
marginBottom: 5,
},
infoLabel: {
fontSize: 14,
fontWeight: 'bold',
color: '#666',
width: 80,
},
infoText: {
fontSize: 14,
color: '#333',
flex: 1,
},
imageGrid: {
flexDirection: 'row',
flexWrap: 'wrap',
gap: 10,
},
gridImage: {
width: 80,
height: 80,
borderRadius: 8,
},
smallButton: {
backgroundColor: '#ef5350',
paddingHorizontal: 12,
paddingVertical: 6,
borderRadius: 6,
},
smallButtonText: {
color: '#fff',
fontSize: 12,
fontWeight: '500',
},
noteSection: {
marginTop: 20,
padding: 15,
backgroundColor: '#e3f2fd',
borderRadius: 8,
borderLeftWidth: 4,
borderLeftColor: '#42a5f5',
},
noteTitle: {
fontSize: 14,
fontWeight: 'bold',
marginBottom: 8,
color: '#1565c0',
},
noteText: {
fontSize: 13,
color: '#1976d2',
marginBottom: 4,
},
warningSection: {
marginTop: 20,
padding: 15,
backgroundColor: '#fff3cd',
borderRadius: 8,
borderLeftWidth: 4,
borderLeftColor: '#ffc107',
},
warningTitle: {
fontSize: 14,
fontWeight: 'bold',
marginBottom: 8,
color: '#856404',
},
warningText: {
fontSize: 13,
color: '#856404',
marginBottom: 4,
},
});
export default CameraRollDemo;
🎨 实际应用场景
react-native-camera-roll 可以应用于以下实际场景:
- 保存用户上传的图片: 保存用户从网络下载的图片到相册
- 保存应用生成的图片: 保存应用内生成的截图、海报等
- 图片管理应用: 开发相册管理、图片编辑等应用
- 社交媒体: 保存用户发布的动态图片
- 电商应用: 保存商品图片到本地
⚠️ 注意事项与最佳实践
1. 权限配置
在使用 camera-roll 之前,需要在 HarmonyOS 的 module.json5 中配置权限:
{
"module": {
"requestPermissions": [
{
"name": "ohos.permission.READ_IMAGEVIDEO",
"reason": "$string:read_imagevideo_reason",
"usedScene": {
"abilities": ["EntryAbility"],
"when": "inuse"
}
},
{
"name": "ohos.permission.WRITE_IMAGEVIDEO",
"reason": "$string:write_imagevideo_reason",
"usedScene": {
"abilities": ["EntryAbility"],
"when": "inuse"
}
}
]
}
}
[!TIP] 部分接口(如 getPhotos、getAlbums、deletePhotos 等)由于 HarmonyOS 安全策略要求,需要申请受限权限才能使用。详细说明:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/restricted-permissions-V5
2. 错误处理
try {
const asset = await CameraRoll.saveAsset(imageUrl);
// 处理成功
} catch (error: any) {
// 处理错误
Alert.alert('错误', error.message || '保存失败');
}
3. HarmonyOS 特殊处理
在 HarmonyOS 上,需要注意:
- 受限权限: 部分接口需要申请受限权限才能使用
- 功能限制: getPhotos、getAlbums、deletePhotos 等接口受限
- 格式支持: saveAsset 接口未完全支持所有格式
- 安全策略: HarmonyOS 对相册访问有严格的安全策略
4. 最佳实践
// ✅ 推荐:使用 try-catch 处理错误
try {
const asset = await CameraRoll.saveAsset(imageUrl);
console.log('保存成功:', asset);
} catch (error) {
console.error('保存失败:', error);
}
// ✅ 推荐:验证 URL 有效性
if (imageUrl && imageUrl.startsWith('http')) {
await CameraRoll.saveAsset(imageUrl);
}
// ✅ 推荐:使用 saveAsset 代替已弃用的方法
// ❌ 不推荐
CameraRoll.save(imageUrl);
CameraRoll.saveToCameraRoll(imageUrl);
// ✅ 推荐
CameraRoll.saveAsset(imageUrl);
5. 性能优化
- 避免频繁保存大量图片
- 使用缩略图进行预览
- 及时清理不需要的图片资源
🧪 测试验证
1. Android 平台测试
npm run android
测试要点:
- 测试保存图片功能
- 测试保存视频功能
- 验证权限申请
- 检查图片质量
2. iOS 平台测试
npm run ios
测试要点:
- 测试保存图片功能
- 测试相册访问
- 验证权限流程
- 检查图片元数据
3. HarmonyOS 平台测试
npm run harmony
测试要点:
- 验证保存图片功能
- 测试权限配置
- 检查图片显示
- 验证受限接口
4. 常见问题排查
问题 1: 保存失败
- 检查权限配置
- 确认 URL 有效性
- 验证文件格式支持
问题 2: 权限拒绝
- 检查权限配置是否正确
- 确认用户已授权
- 验证受限权限申请
问题 3: 图片格式不支持
- 确认文件格式是否在支持列表中
- 检查文件是否损坏
- 尝试使用其他格式
📊 对比:原生相册 vs react-native-camera-roll
| 特性 | 原生相册 | react-native-camera-roll |
|---|---|---|
| 跨平台一致性 | ❌ | ✅ 完全一致 |
| API 简洁性 | ⚠️ 复杂 | ✅ 简洁 |
| 保存功能 | ✅ 完整 | ✅ 完整 |
| 读取功能 | ✅ 完整 | ⚠️ 受限 |
| 删除功能 | ✅ 完整 | ❌ 未适配 |
| 格式支持 | ✅ 广泛 | ⚠️ 部分 |
📝 总结
通过集成 react-native-camera-roll,我们为项目添加了强大的相册管理能力。这个库提供了保存图片/视频到相册的核心功能,支持多种格式,并且完全跨平台兼容。
关键要点回顾
- ✅ 安装依赖:
npm install @react-native-ohos/camera-roll - ✅ 配置平台: 通过 har 包或直接链接源码,配置 CMakeLists.txt、PackageProvider.cpp、RNPackagesFactory.ts(仅 0.77 需要)
- ✅ 集成代码: 使用
CameraRoll.saveAsset保存图片/视频 - ✅ 支持功能: 保存图片/视频、获取元数据等
- ✅ 重要: HarmonyOS 部分接口受限,需要申请受限权限
实际效果
- Android: 原生相册体验
- iOS: 高质量的相册管理
- HarmonyOS: 一致的保存体验
已知限制
- ❌ getPhotos、getAlbums 接口受限(需要受限权限)
- ❌ deletePhotos 功能暂未适配
- ⚠️ 部分接口因安全策略无法使用
- ⚠️ saveAsset 接口未完全支持所有格式
遗留问题
- deletePhotos 删除图片/视频未实现 HarmonyOS 化
- harmonyRefreshGallerySelection 无对应接口
- 部分接口因安全策略无法使用
- saveAsset 接口未完全支持
希望这篇教程能帮助你顺利集成 react-native-camera-roll,构建出色的相册管理体验!
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
更多推荐

所有评论(0)