欢迎光临
我们一直在努力

ReactNative for Harmony 项目鸿蒙化三方库集成实战:react-native-camera-roll(保存到相册)

在这里插入图片描述

📋 前言

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)
    未经允许不得转载:171主机测评 » ReactNative for Harmony 项目鸿蒙化三方库集成实战:react-native-camera-roll(保存到相册)
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址