React Native前台服务中相机未就绪问题求助
问题:React Native前台服务中后台拍照失败,提示相机未就绪
应用前台运行时拍照功能正常,但切换到后台(前台服务仍处于活动状态)时相机停止工作,抛出以下错误:
WARN Possible Unhandled Promise Rejection (id: 3):
session/camera-not-ready: [session/camera-not-ready] The Camera is not ready yet! Wait for the onInitialized() callback!
使用notifee实现前台服务,react-native-vision-camera实现相机功能。
核心问题分析
当应用退到后台时,React Native的UI组件会被暂停或销毁,<Camera>组件的相机会话也会随之关闭。你当前的代码直接依赖UI层的cameraRef调用拍照,后台场景下该实例已无法正常工作,因此触发"相机未就绪"的错误。同时,前台服务回调中直接更新UI状态的逻辑在后台也会失效。
解决步骤
- 分离相机逻辑与UI层:使用react-native-vision-camera的低级API(
CameraManager)在后台服务中独立管理相机会话,不依赖UI组件实例。 - 补充权限配置:申请后台相机相关权限,确保前台服务有权限访问相机。
- 管理相机生命周期:前台服务启动时初始化相机,停止时释放资源,避免内存泄漏。
- 避免后台UI更新:后台拍照后将路径保存到本地存储,仅在应用前台时更新UI显示。
修改后的代码示例
import React, {useRef, useState, useEffect} from 'react'; import { ActivityIndicator, Image, TouchableOpacity, Text, View, StyleSheet, PermissionsAndroid } from 'react-native'; import {useCameraDevices, Camera, CameraManager} from 'react-native-vision-camera'; import {useIsForeground} from './hooks/useIsForeground'; import RNFS from 'react-native-fs'; import notifee from '@notifee/react-native'; const HelloWorldApp = () => { const isAppForeground = useIsForeground(); const [finalPath, setPhotoPath] = useState(''); const devices = useCameraDevices(); const device = devices.front; let cameraSession = null; let intervalId = null; // 申请必要权限 const requestPermissions = async () => { const cameraPerm = await PermissionsAndroid.request(PermissionsAndroid.PERMISSIONS.CAMERA); const fgServicePerm = await PermissionsAndroid.request(PermissionsAndroid.PERMISSIONS.FOREGROUND_SERVICE); const fgCameraPerm = await PermissionsAndroid.request(PermissionsAndroid.PERMISSIONS.FOREGROUND_SERVICE_CAMERA); return cameraPerm === 'granted' && fgServicePerm === 'granted' && fgCameraPerm === 'granted'; }; // 后台拍照逻辑 const startBackgroundCamera = async () => { try { // 初始化相机会话 cameraSession = await CameraManager.openCamera(device.id, { format: device.formats[0], fps: 30, }); intervalId = setInterval(async () => { const snapshot = await CameraManager.takeSnapshot(cameraSession, { quality: 20, skipMetadata: true, }); // 保存到公共目录,确保后台可访问 const saveDir = `${RNFS.ExternalStorageDirectoryPath}/camera_snapshots`; await RNFS.mkdir(saveDir, {NSURLIsDirectoryKey: true}); const savePath = `${saveDir}/${Date.now()}.jpg`; await RNFS.moveFile(snapshot.path, savePath); // 仅前台时更新UI if (isAppForeground) setPhotoPath(`file://${savePath}`); }, 2000); } catch (err) { console.error('后台相机启动失败:', err); } }; // 停止后台相机 const stopBackgroundCamera = () => { if (intervalId) clearInterval(intervalId); if (cameraSession) { CameraManager.closeCamera(cameraSession); cameraSession = null; } }; useEffect(() => { // 注册前台服务 notifee.registerForegroundService(async () => { await startBackgroundCamera(); // 保持服务运行,点击通知停止服务 return new Promise((resolve) => { const unsubscribe = notifee.onNotificationPress(() => { stopBackgroundCamera(); resolve(); unsubscribe(); }); }); }); // 组件卸载时清理资源 return () => stopBackgroundCamera(); }, []); if (device == null) { return <ActivityIndicator style={styles.indicator} size="large" />; } async function onDisplayNotification() { const hasPerms = await requestPermissions(); if (!hasPerms) { console.log('权限不足,无法启动服务'); return; } const channelId = await notifee.createChannel({ id: 'camera_service', name: '相机后台服务', importance: notifee.Importance.HIGH, }); await notifee.displayNotification({ title: '后台拍照服务运行中', body: '点击通知停止服务', android: { channelId, asForegroundService: true, color: '#FF0000', colorized: true, actions: [{title: '停止', pressAction: {id: 'stop'}}], }, }); } return ( <View style={styles.container}> {isAppForeground && ( <> <Camera style={styles.camera} device={device} isActive={true} /> <Image source={{uri: finalPath + '?' + new Date()}} style={styles.image} /> </> )} <TouchableOpacity style={styles.button} onPress={onDisplayNotification}> <Text>启动后台拍照服务</Text> </TouchableOpacity> </View> ); }; const styles = StyleSheet.create({ container: {flex: 1, justifyContent: 'center', alignItems: 'center'}, camera: {width: '100%', height: '60%'}, image: {width: 200, height: 200, margin: 20}, button: {padding: 15, backgroundColor: '#2196F3', borderRadius: 8}, indicator: {flex: 1, justifyContent: 'center', alignItems: 'center'}, }); export default HelloWorldApp;
额外配置说明
- AndroidManifest.xml需添加以下权限:
<uses-permission android:name="android.permission.CAMERA" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE_CAMERA" /> <uses-feature android:name="android.hardware.camera" /> <uses-feature android:name="android.hardware.camera.front" /> - iOS平台需开启
Background Modes中的Camera权限,且需向Apple说明后台拍照的使用场景,否则App Store审核可能被拒。 - 后台拍照会持续消耗电量,建议添加明确的启停控制逻辑,避免长时间运行。
内容的提问来源于stack exchange,提问作者Mfa Xyz
相关产品推荐
相关产品推荐

