1. React Native鸿蒙开发环境搭建
在开始实现ImagePicker拍照上传功能之前,我们需要先搭建React Native在鸿蒙系统上的开发环境。鸿蒙(HarmonyOS)作为华为推出的分布式操作系统,与传统的Android/iOS开发环境有所不同。
1.1 开发工具准备
首先需要安装以下基础工具:
- Node.js 16.x或更高版本(推荐使用LTS版本)
- Java Development Kit (JDK) 11
- HarmonyOS SDK
- React Native CLI
安装完成后,建议配置环境变量:
bash复制# 在~/.bashrc或~/.zshrc中添加
export JAVA_HOME=/path/to/jdk-11
export PATH=$JAVA_HOME/bin:$PATH
export HARMONY_HOME=/path/to/harmony/sdk
1.2 创建React Native鸿蒙项目
使用React Native CLI初始化项目:
bash复制npx react-native init RNHarmonyImagePicker --version 0.70.0
cd RNHarmonyImagePicker
然后添加鸿蒙平台支持:
bash复制npm install @react-native-harmony/harmony --save
npx react-native-harmony init
1.3 鸿蒙模拟器配置
鸿蒙开发需要使用特定的模拟器:
- 打开DevEco Studio
- 进入Tools > Device Manager
- 选择Phone设备类型
- 下载并安装HarmonyOS 3.0或更高版本的模拟器
注意:鸿蒙模拟器与Android模拟器不兼容,需要单独配置。如果遇到性能问题,可以考虑使用真机调试。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. ImagePicker模块集成
2.1 安装React Native ImagePicker
在项目中添加react-native-image-picker依赖:
bash复制npm install react-native-image-picker
对于鸿蒙平台,需要额外配置原生模块:
bash复制npx react-native-harmony add react-native-image-picker
2.2 鸿蒙权限配置
在鸿蒙应用中,需要在config.json中添加相机和存储权限:
json复制{
"module": {
"reqPermissions": [
{
"name": "ohos.permission.CAMERA"
},
{
"name": "ohos.permission.READ_MEDIA"
},
{
"name": "ohos.permission.WRITE_MEDIA"
}
]
}
}
2.3 鸿蒙特定适配
由于鸿蒙的文件系统与Android有所不同,需要对ImagePicker进行一些适配:
javascript复制// 在App.js中添加鸿蒙平台判断
import { Platform } from 'react-native';
const isHarmony = Platform.OS === 'harmony';
3. 拍照功能实现
3.1 基本拍照功能
实现拍照功能的核心代码如下:
javascript复制import { launchCamera } from 'react-native-image-picker';
const options = {
mediaType: 'photo',
quality: 0.8,
saveToPhotos: true,
cameraType: 'back',
includeBase64: false,
};
const takePhoto = async () => {
try {
const result = await launchCamera(options);
if (result.didCancel) {
console.log('User cancelled image picker');
} else if (result.errorCode) {
console.log('ImagePicker Error: ', result.errorMessage);
} else {
const source = { uri: result.assets[0].uri };
setImage(source);
// 这里可以添加上传逻辑
}
} catch (error) {
console.error('Camera error:', error);
}
};
3.2 鸿蒙文件路径处理
鸿蒙系统的文件URI格式与Android不同,需要进行转换:
javascript复制const getHarmonyFilePath = (uri) => {
if (!isHarmony) return uri;
// 鸿蒙URI格式转换逻辑
const prefix = 'file://';
if (uri.startsWith(prefix)) {
return uri.substring(prefix.length);
}
return uri;
};
4. 图片上传功能
4.1 上传服务端实现
创建一个上传函数,处理图片上传到服务器的过程:
javascript复制const uploadImage = async (fileUri) => {
const actualUri = isHarmony ? getHarmonyFilePath(fileUri) : fileUri;
const formData = new FormData();
formData.append('image', {
uri: actualUri,
type: 'image/jpeg',
name: 'photo.jpg',
});
try {
const response = await fetch('https://your-api-endpoint/upload', {
method: 'POST',
body: formData,
headers: {
'Content-Type': 'multipart/form-data',
},
});
const data = await response.json();
console.log('Upload success:', data);
return data;
} catch (error) {
console.error('Upload error:', error);
throw error;
}
};
4.2 进度显示与错误处理
增强上传功能,添加进度显示和更好的错误处理:
javascript复制const [uploadProgress, setUploadProgress] = useState(0);
const uploadWithProgress = (fileUri) => {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
const actualUri = isHarmony ? getHarmonyFilePath(fileUri) : fileUri;
xhr.upload.onprogress = (event) => {
if (event.lengthComputable) {
const progress = Math.round((event.loaded / event.total) * 100);
setUploadProgress(progress);
}
};
xhr.onload = () => {
if (xhr.status >= 200 && xhr.status < 300) {
resolve(xhr.response);
} else {
reject(new Error('Upload failed'));
}
};
xhr.onerror = () => reject(new Error('Upload error'));
const formData = new FormData();
formData.append('image', {
uri: actualUri,
type: 'image/jpeg',
name: 'photo.jpg',
});
xhr.open('POST', 'https://your-api-endpoint/upload');
xhr.send(formData);
});
};
5. 完整组件实现
5.1 完整示例代码
结合拍照和上传功能,实现一个完整的图片上传组件:
javascript复制import React, { useState } from 'react';
import { View, Text, Button, Image, ActivityIndicator } from 'react-native';
import { launchCamera } from 'react-native-image-picker';
const ImageUploader = () => {
const [image, setImage] = useState(null);
const [uploading, setUploading] = useState(false);
const [uploadProgress, setUploadProgress] = useState(0);
const [uploadResult, setUploadResult] = useState(null);
const takePhoto = async () => {
try {
const result = await launchCamera({
mediaType: 'photo',
quality: 0.8,
saveToPhotos: true,
});
if (!result.didCancel && !result.errorCode) {
setImage(result.assets[0].uri);
}
} catch (error) {
console.error('Camera error:', error);
}
};
const uploadImage = async () => {
if (!image) return;
setUploading(true);
setUploadProgress(0);
try {
const result = await uploadWithProgress(image);
setUploadResult(result);
} catch (error) {
console.error('Upload failed:', error);
} finally {
setUploading(false);
}
};
return (
<View style={{ flex: 1, padding: 20 }}>
{image && (
<Image
source={{ uri: image }}
style={{ width: 200, height: 200, marginBottom: 20 }}
/>
)}
<Button title="Take Photo" onPress={takePhoto} />
{image && (
<Button
title={uploading ? 'Uploading...' : 'Upload Image'}
onPress={uploadImage}
disabled={uploading}
/>
)}
{uploading && (
<View>
<ActivityIndicator size="small" color="#0000ff" />
<Text>Progress: {uploadProgress}%</Text>
</View>
)}
{uploadResult && (
<Text>Upload successful! URL: {uploadResult.url}</Text>
)}
</View>
);
};
export default ImageUploader;
5.2 样式优化与用户体验
添加一些样式和用户体验优化:
javascript复制const styles = {
container: {
flex: 1,
padding: 20,
alignItems: 'center',
justifyContent: 'center',
},
image: {
width: 300,
height: 300,
marginBottom: 20,
borderRadius: 10,
borderWidth: 1,
borderColor: '#ddd',
},
button: {
marginVertical: 10,
width: 200,
},
progressContainer: {
marginTop: 20,
alignItems: 'center',
},
successText: {
marginTop: 20,
color: 'green',
textAlign: 'center',
},
errorText: {
marginTop: 20,
color: 'red',
textAlign: 'center',
},
};
6. 测试与调试
6.1 鸿蒙真机调试
在鸿蒙设备上调试的步骤:
- 在设备上启用开发者模式(设置 > 关于手机 > 多次点击版本号)
- 启用USB调试
- 连接电脑并授权调试
- 运行项目:
bash复制npx react-native run-harmony
6.2 常见问题解决
-
相机无法启动:
- 检查权限是否已授予
- 确认config.json中已声明相机权限
- 重启应用或设备
-
图片保存失败:
- 检查存储权限
- 确认鸿蒙文件路径处理正确
- 尝试使用绝对路径
-
上传进度不更新:
- 确保服务器支持进度报告
- 检查网络连接
- 测试使用不同的网络环境
-
鸿蒙特定问题:
- 确保使用最新版本的react-native-harmony
- 检查鸿蒙API级别兼容性
- 查阅鸿蒙开发者文档获取最新API变更
7. 性能优化
7.1 图片压缩处理
在上传前对图片进行压缩:
javascript复制import { manipulateAsync } from 'expo-image-manipulator';
const compressImage = async (uri) => {
const result = await manipulateAsync(
uri,
[{ resize: { width: 800 } }],
{ compress: 0.7, format: 'jpeg' }
);
return result.uri;
};
7.2 缓存处理
实现本地缓存以减少重复上传:
javascript复制import { AsyncStorage } from 'react-native';
const cacheImage = async (uri) => {
try {
const cachedImages = await AsyncStorage.getItem('cachedImages') || '{}';
const parsedCache = JSON.parse(cachedImages);
const cacheKey = `image_${Date.now()}`;
parsedCache[cacheKey] = uri;
await AsyncStorage.setItem('cachedImages', JSON.stringify(parsedCache));
return cacheKey;
} catch (error) {
console.error('Caching failed:', error);
return null;
}
};
7.3 上传队列管理
对于批量上传,实现上传队列:
javascript复制class UploadQueue {
constructor() {
this.queue = [];
this.activeUploads = 0;
this.maxConcurrent = 3;
}
add(task) {
return new Promise((resolve, reject) => {
this.queue.push({ task, resolve, reject });
this.processQueue();
});
}
processQueue() {
while (this.queue.length > 0 && this.activeUploads < this.maxConcurrent) {
const { task, resolve, reject } = this.queue.shift();
this.activeUploads++;
task()
.then(resolve)
.catch(reject)
.finally(() => {
this.activeUploads--;
this.processQueue();
});
}
}
}
// 使用示例
const uploadQueue = new UploadQueue();
const uploadAllImages = async (imageUris) => {
const results = await Promise.all(
imageUris.map(uri => uploadQueue.add(() => uploadImage(uri)))
);
return results;
};
8. 安全考虑
8.1 图片安全处理
-
服务器端验证:
- 检查文件类型
- 限制文件大小
- 扫描恶意内容
-
客户端处理:
- 限制图片分辨率
- 剥离EXIF数据
- 使用安全的内容URI
8.2 认证与授权
在上传请求中添加认证:
javascript复制const uploadImageWithAuth = async (fileUri, authToken) => {
const formData = new FormData();
formData.append('image', {
uri: fileUri,
type: 'image/jpeg',
name: 'photo.jpg',
});
const response = await fetch('https://your-api-endpoint/upload', {
method: 'POST',
body: formData,
headers: {
'Content-Type': 'multipart/form-data',
'Authorization': `Bearer ${authToken}`,
},
});
return response.json();
};
8.3 数据加密
对于敏感图片,考虑客户端加密:
javascript复制import CryptoJS from 'crypto-js';
const encryptImage = async (uri) => {
const response = await fetch(uri);
const blob = await response.blob();
const arrayBuffer = await blob.arrayBuffer();
const wordArray = CryptoJS.lib.WordArray.create(arrayBuffer);
const secret = 'your-secret-key';
const encrypted = CryptoJS.AES.encrypt(wordArray, secret).toString();
return encrypted;
};
9. 高级功能扩展
9.1 多图选择与上传
扩展组件以支持多图选择:
javascript复制const [selectedImages, setSelectedImages] = useState([]);
const selectMultipleImages = async () => {
try {
const result = await launchImageLibrary({
mediaType: 'photo',
quality: 0.8,
selectionLimit: 5, // 最多选择5张
});
if (!result.didCancel && !result.errorCode) {
setSelectedImages(result.assets.map(asset => asset.uri));
}
} catch (error) {
console.error('Image selection error:', error);
}
};
const uploadMultipleImages = async () => {
if (selectedImages.length === 0) return;
setUploading(true);
try {
const results = await uploadAllImages(selectedImages);
console.log('All uploads completed:', results);
} catch (error) {
console.error('Batch upload failed:', error);
} finally {
setUploading(false);
}
};
9.2 图片编辑功能
集成简单的图片编辑功能:
javascript复制import ImageEditor from '@react-native-community/image-editor';
const cropImage = async (uri, cropData) => {
try {
const croppedUri = await ImageEditor.cropImage(uri, cropData);
return croppedUri;
} catch (error) {
console.error('Cropping failed:', error);
return uri; // 返回原始URI如果裁剪失败
}
};
// 使用示例
const handleCrop = async () => {
if (!image) return;
const cropData = {
offset: { x: 0, y: 0 },
size: { width: 300, height: 300 },
displaySize: { width: 150, height: 150 },
resizeMode: 'contain',
};
const croppedUri = await cropImage(image, cropData);
setImage(croppedUri);
};
9.3 离线功能支持
实现离线拍照和队列上传:
javascript复制// 离线状态检测
const [isOnline, setIsOnline] = useState(true);
useEffect(() => {
const handleConnectivityChange = (state) => {
setIsOnline(state.isConnected);
};
NetInfo.addEventListener(handleConnectivityChange);
return () => {
NetInfo.removeEventListener(handleConnectivityChange);
};
}, []);
// 离线处理
const handleOfflineUpload = async (uri) => {
if (isOnline) {
return uploadImage(uri);
} else {
// 保存到离线队列
const queue = await AsyncStorage.getItem('offlineUploadQueue') || '[]';
const parsedQueue = JSON.parse(queue);
parsedQueue.push(uri);
await AsyncStorage.setItem('offlineUploadQueue', JSON.stringify(parsedQueue));
return { status: 'queued', uri };
}
};
// 网络恢复后处理队列
const processOfflineQueue = async () => {
const queue = await AsyncStorage.getItem('offlineUploadQueue') || '[]';
const parsedQueue = JSON.parse(queue);
if (parsedQueue.length > 0 && isOnline) {
const results = await uploadAllImages(parsedQueue);
await AsyncStorage.setItem('offlineUploadQueue', '[]');
return results;
}
return [];
};
10. 项目构建与发布
10.1 鸿蒙应用打包
- 配置应用签名
- 设置应用图标和启动画面
- 生成HAP包:
bash复制npx react-native-harmony build
10.2 应用商店发布
- 注册华为开发者账号
- 准备应用元数据(描述、截图等)
- 提交应用到AppGallery审核
- 管理应用版本更新
10.3 持续集成
设置自动化构建流程:
- 配置GitHub Actions或Jenkins
- 自动化测试
- 自动打包和部署
yaml复制# 示例GitHub Actions配置
name: Build and Deploy
on:
push:
branches: [ main ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Use Node.js
uses: actions/setup-node@v2
with:
node-version: '16'
- name: Install dependencies
run: npm install
- name: Build Harmony package
run: npx react-native-harmony build
- name: Upload artifact
uses: actions/upload-artifact@v2
with:
name: harmony-app
path: build/outputs/hap/
