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

Expo Camera EAS构建启动录像时崩溃问题求助

解决Expo Camera自动录像在EAS构建APK中崩溃/不启动的问题

问题核心

  • Expo Go环境下,通过onCameraReady回调触发自动录像可正常工作
  • EAS构建生成APK后,应用崩溃或无法自动启动录像
  • 首次启动需手动点击录制按钮才能触发录像

解决方案

1. 完善权限检查与等待逻辑

权限请求是异步操作,原代码未等待权限完全授予就可能触发录像,导致EAS构建环境下权限未就绪引发崩溃。修改权限检查逻辑,确保所有权限通过后再启动录像:

async function permission() {
  const cameraPerm = await Camera.requestCameraPermissionsAsync();
  const micPerm = await Camera.requestMicrophonePermissionsAsync();
  const mediaPerm = await MediaLibrary.requestPermissionsAsync();
  
  setcameraStatus(cameraPerm);
  setmicStatus(micPerm);
  setmediaStatus(mediaPerm);
  
  // 返回权限是否全部通过
  return cameraPerm.granted && micPerm.granted && mediaPerm.granted;
}

2. 替换onCameraReady触发,改用状态监听启动录像

EAS构建的原生环境中,onCameraReady触发时机可能早于相机ref完全初始化或权限就绪。改用useEffect监听相机ref和权限状态,确保两者都就绪后启动录像:

useEffect(() => {
  async function initAndRecord() {
    const allPermissionsGranted = await permission();
    setStatusBarTranslucent(true);
    
    // 等待相机ref初始化完成 + 权限全部通过
    const waitForCamera = setInterval(() => {
      if (camera && allPermissionsGranted) {
        clearInterval(waitForCamera);
        record();
      }
    }, 100);
    
    return () => clearInterval(waitForCamera);
  }
  
  initAndRecord();
}, []);

// 移除Camera组件的onCameraReady属性
<Camera
  ref={(ref) => setcamera(ref)} 
  style={{flex: 1, aspectRatio: 9/16}}
  ratio={'16:9'}
  type={type}
>

3. 修复record函数的异步错误处理

原函数混用await与then,且无错误捕获,崩溃可能源于未处理的异常。修改为统一的async/await写法并添加错误处理:

const record = async () => {
  if (!camera) return; // 确保相机ref存在
  
  try {
    setstartRecording(true);
    const video = await camera.recordAsync();
    await MediaLibrary.saveToLibraryAsync(video.uri);
  } catch (error) {
    console.error('录像失败:', error);
    setstartRecording(false);
    Alert.alert('错误', '无法启动录像,请检查权限设置');
  }
};

4. 确认EAS构建的权限配置

在app.json中明确声明所需权限,确保EAS构建时正确打包权限配置:

{
  "expo": {
    "android": {
      "permissions": [
        "CAMERA",
        "RECORD_AUDIO",
        "WRITE_EXTERNAL_STORAGE",
        "READ_EXTERNAL_STORAGE"
      ]
    }
  }
}

修改后的完整代码

import { Camera, CameraType } from 'expo-camera';
import { setStatusBarTranslucent } from 'expo-status-bar';
import { useEffect, useState } from 'react';
import { Alert, Dimensions, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
import { MaterialIcons } from '@expo/vector-icons';
import { Fontisto } from '@expo/vector-icons';
import * as MediaLibrary from 'expo-media-library';

export default function App() {
  const [type, settype] = useState(CameraType.back);
  const [camera, setcamera] = useState(null);
  const [cameraStatus, setcameraStatus] = useState({});
  const [micStatus, setmicStatus] = useState({});
  const [mediaStatus, setmediaStatus] = useState({});
  const [startRecording, setstartRecording] = useState(false);
  const [timer, settimer] = useState(0);

  async function permission() {
    const cameraPerm = await Camera.requestCameraPermissionsAsync();
    const micPerm = await Camera.requestMicrophonePermissionsAsync();
    const mediaPerm = await MediaLibrary.requestPermissionsAsync();
    
    setcameraStatus(cameraPerm);
    setmicStatus(micPerm);
    setmediaStatus(mediaPerm);
    
    return cameraPerm.granted && micPerm.granted && mediaPerm.granted;
  }

  useEffect(() => {
    async function initAndRecord() {
      const allPermissionsGranted = await permission();
      setStatusBarTranslucent(true);
      
      const waitForCamera = setInterval(() => {
        if (camera && allPermissionsGranted) {
          clearInterval(waitForCamera);
          record();
        }
      }, 100);
      
      return () => clearInterval(waitForCamera);
    }
    
    initAndRecord();
  }, []);

  useEffect(() => {
    let interval = null;
    if (startRecording) {
      interval = setInterval(() => {
        settimer(prev => prev + 1);
      }, 1000);
    } else {
      clearInterval(interval);
      settimer(0);
    }

    return () => clearInterval(interval);
  }, [startRecording]);

  const record = async () => {
    if (!camera) return;
    
    try {
      setstartRecording(true);
      const video = await camera.recordAsync();
      await MediaLibrary.saveToLibraryAsync(video.uri);
    } catch (error) {
      console.error('录像失败:', error);
      setstartRecording(false);
      Alert.alert('错误', '无法启动录像,请检查权限设置');
    }
  };

  const toggleCameraType = () => {
    setstartRecording(false);
    settype(current => (current === CameraType.back ? CameraType.front : CameraType.back));
  };

  const stopRecording = () => {
    if (camera) {
      camera.stopRecording();
      setstartRecording(false);
    }
  };

  return (
    <Camera
      ref={(ref) => setcamera(ref)} 
      style={{flex: 1, aspectRatio: 9/16}}
      ratio={'16:9'}
      type={type}
    >
      <View style={{position: 'absolute', flexDirection: 'row', bottom: 0, padding: 48, justifyContent: 'space-between', width: Dimensions.get('window').width}}>
        <View
          style={{
            width: 61, 
            height: 61, 
            borderWidth: 3, 
            borderColor: 'white', 
            borderRadius: 100, 
            backgroundColor: 'black',
            alignItems: 'center',
            justifyContent: 'center'
          }}>
          <Text
            style={{
              color: 'white',
              fontSize: 34,
              fontWeight: 'bold'
            }}>{timer}</Text>
        </View>
        <TouchableOpacity onPress={startRecording ? stopRecording : record}>
          <Fontisto name="record" size={64} color={startRecording ? "red" : "white"} />
        </TouchableOpacity>
        <TouchableOpacity onPress={toggleCameraType}>
          <MaterialIcons name="flip-camera-android" size={64} color="white" />
        </TouchableOpacity>
      </View>
    </Camera>      
  );
}

const styles = StyleSheet.create({
  camera: {
    flex: 1,
  }
});

内容的提问来源于stack exchange,提问作者computer

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.07 14:20:23