使用Expo Camera录制时导航离开,Expo应用彻底无响应求助
解决Expo Camera录制时导航导致应用冻结的问题
看起来你遇到的核心问题是录制状态下导航时,相机资源没有被正确释放,导致Promise阻塞或内存泄漏。虽然你已经加了blur监听,但代码里的几个异步处理缺陷导致资源没及时清理,最终引发应用无响应。我来帮你一步步修复:
问题分析
先拆解你代码里的几个关键问题:
stopVideo直接调用camera.stopRecording()但没有等待异步操作完成,导致导航时录制还在后台运行,持续占用相机资源。takeVideo里的return写法错误(逗号表达式),而且定时器没有清理,可能导致重复调用停止录制,引发冲突。- Blur事件里调用
stopVideo但没有await,导航操作可能在录制停止前就执行了,资源没来得及释放。 - 用
useState存储相机实例,可能因为异步更新导致获取不到最新的相机引用。
修复方案
1. 改用useRef存储相机实例
useState的更新是异步的,用useRef存相机实例更可靠,能实时获取到最新的相机对象:
const cameraRef = useRef(null); // 替换原来的 const [camera, setCamera] = useState(null)
2. 修复stopVideo函数,正确处理异步
camera.stopRecording()返回一个Promise,必须等待它完成才能确保资源彻底释放:
const stopVideo = useCallback(async () => { if (!cameraRef.current || !recording) return; // 避免重复调用引发错误 setRecording(false); try { await cameraRef.current.stopRecording(); // 等待停止录制的异步操作完成 } catch (error) { console.error('停止录制失败:', error); } }, [recording]);
3. 重构takeVideo,清理定时器并正确处理异步
确保录制完成/手动停止后清理定时器,避免重复执行停止操作:
const takeVideo = useCallback(async () => { if (!cameraRef.current || recording) return; setRecording(true); let timeoutId; try { // 设置30秒自动停止的定时器 timeoutId = setTimeout(() => stopVideo(), 30000); const data = await cameraRef.current.recordAsync({ quality: Camera.Constants.VideoQuality['4:3'], }); setRecord(data.uri); // 录制完成后保存视频地址 } catch (error) { console.error('录制视频失败:', error); } finally { clearTimeout(timeoutId); // 不管成功失败都清理定时器 if (recording) setRecording(false); // 确保录制状态被重置 } }, [recording, stopVideo]);
4. 修改Blur监听,确保await停止录制完成
在页面失焦时,必须等待录制完全停止后再执行导航相关操作:
useEffect(() => { const unsubscribe = navigation.addListener('blur', async () => { try { await stopVideo(); // 等待录制停止完成 setRecord(null); // 清理视频地址 // cameraRef不需要手动置空,组件卸载后会自动释放引用 } catch (error) { console.error('处理页面失焦事件失败:', error); } }); return unsubscribe; }, [navigation, stopVideo]);
5. 优化Camera组件的条件渲染
保持screenIsFocused的条件渲染逻辑,但改用useRef绑定相机实例:
{screenIsFocused && <Camera ref={cameraRef} style={styles.fixedRatio} type={type} ratio={'4:3'} />}
完整修改后的代码
import { useRef, useState, useEffect, useCallback } from 'react'; import { View } from 'react-native'; import { Camera } from 'expo-camera'; import { IconButton } from '@react-native-material/core'; import { useIsFocused } from '@react-navigation/native'; function RecordingBlock({ navigation }) { const cameraRef = useRef(null); const [record, setRecord] = useState(null); const [recording, setRecording] = useState(false); const screenIsFocused = useIsFocused(); // 假设type是你定义的相机类型,比如后置摄像头 const type = Camera.Constants.Type.back; const stopVideo = useCallback(async () => { if (!cameraRef.current || !recording) return; setRecording(false); try { await cameraRef.current.stopRecording(); } catch (error) { console.error('停止录制失败:', error); } }, [recording]); const takeVideo = useCallback(async () => { if (!cameraRef.current || recording) return; setRecording(true); let timeoutId; try { timeoutId = setTimeout(() => stopVideo(), 30000); const data = await cameraRef.current.recordAsync({ quality: Camera.Constants.VideoQuality['4:3'], }); setRecord(data.uri); } catch (error) { console.error('录制视频失败:', error); } finally { clearTimeout(timeoutId); if (recording) setRecording(false); } }, [recording, stopVideo]); useEffect(() => { const unsubscribe = navigation.addListener('blur', async () => { try { await stopVideo(); setRecord(null); } catch (error) { console.error('处理页面失焦事件失败:', error); } }); return unsubscribe; }, [navigation, stopVideo]); return ( <View> {screenIsFocused && ( <Camera ref={cameraRef} style={styles.fixedRatio} type={type} ratio={'4:3'} /> )} <View> {recording ? ( <IconButton onPress={stopVideo} /> ) : ( <IconButton onPress={takeVideo} /> )} </View> </View> ); } export default RecordingBlock;
额外排查建议
- 确保你的
expo-camera和expo版本是最新的,旧版本可能存在已知的相机资源泄漏问题。 - 尽量在真机上测试,模拟器有时候会有相机相关的兼容性问题。
- 检查是否有其他组件在同时占用相机资源,比如权限请求、预览组件等。
内容的提问来源于stack exchange,提问作者Charlie
相关产品推荐
相关产品推荐

