You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用Expo Camera录制时导航离开,Expo应用彻底无响应求助

解决Expo Camera录制时导航导致应用冻结的问题

看起来你遇到的核心问题是录制状态下导航时,相机资源没有被正确释放,导致Promise阻塞或内存泄漏。虽然你已经加了blur监听,但代码里的几个异步处理缺陷导致资源没及时清理,最终引发应用无响应。我来帮你一步步修复:

问题分析

先拆解你代码里的几个关键问题:

  1. stopVideo 直接调用 camera.stopRecording() 但没有等待异步操作完成,导致导航时录制还在后台运行,持续占用相机资源。
  2. takeVideo 里的return写法错误(逗号表达式),而且定时器没有清理,可能导致重复调用停止录制,引发冲突。
  3. Blur事件里调用 stopVideo 但没有await,导航操作可能在录制停止前就执行了,资源没来得及释放。
  4. 用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.04 10:01:45