Expo相机因Image Picker权限异常,寻求技术解决方案
React Native Expo ImagePicker 授权后重启应用相机失效问题解决
问题描述
- 生产环境首次启动应用(无相机权限),点击相机组件触发权限请求后功能正常
- 授权权限后关闭并重新打开应用,相机功能失效
- 在系统设置中移除权限后重新打开应用,功能恢复正常
报错信息
Error: Call to function 'ExponentImagePicker.launchCameraAsync' has been rejected. java.lang.IllegalStateException: Attempting to launch an unregistered ActivityResultLauncher with contract expo.modules.imagepicker.contracts.CameraContract@ebffc59 and input CameraContractOptions(uri=content://com.test.testapp.ImagePickerFileProvider/cached_expo_files/ImagePicker/0f946a22-9efc-4706-8b23-d2d3fc8711f0.jpeg, options=expo.modules.imagepicker.ImagePickerOptions@c77c01e). You must ensure the ActivityResultLauncher is registered before calling launch()] code: 'ERR_UNEXPECTED' }
原因分析
这个错误源于Android平台上Expo ImagePicker的内部机制:ActivityResultLauncher仅在权限请求流程中完成注册。当应用重启且相机权限已处于授权状态时,代码跳过了requestCameraPermissionsAsync步骤,导致launchCameraAsync调用时启动器未完成注册,触发异常。
解决方案
1. 组件挂载时预检查权限(强制初始化启动器)
在组件挂载阶段主动调用getCameraPermissionsAsync,触发ImagePicker内部的启动器注册逻辑,避免仅在点击时才执行权限检查。
2. 升级Expo ImagePicker到最新稳定版
部分旧版本存在该注册逻辑的bug,升级到最新版可解决此类问题:
npx expo install expo-image-picker@latest
3. 确保app.json中配置正确权限
确认app.json的android.permissions包含相机相关权限:
{ "expo": { "android": { "permissions": [ "CAMERA", "READ_MEDIA_IMAGES", "WRITE_EXTERNAL_STORAGE" ] } } }
修改后的代码
import React, { useState, useEffect } from "react"; import { View, Text, TouchableOpacity, Image, Alert } from "react-native"; import { styles } from "./styles"; import { Feather } from "@expo/vector-icons"; import * as ImagePicker from "expo-image-picker"; import * as MediaLibrary from "expo-media-library"; import { globalStyles } from "../../../../globalStyles"; const ImageInspect = React.memo(function ImageType({ data, setData, title, }) { const [photo, setPhoto] = useState(); // 组件挂载时预检查权限,初始化ImagePicker启动器 useEffect(() => { const initCameraPermissions = async () => { await ImagePicker.getCameraPermissionsAsync(); }; initCameraPermissions(); }, []); useEffect(() => { if (!!data?.title?.files) { setPhoto(data?.title?.files[0]); } }, [data]); const getImageOfCamera = async () => { try { let permissionResult = await ImagePicker.getCameraPermissionsAsync(); if (permissionResult.status !== "granted") { permissionResult = await ImagePicker.requestCameraPermissionsAsync(); } if (permissionResult.status !== "granted") { alert("必须开启相机权限才能使用拍照功能"); return; } const result = await ImagePicker.launchCameraAsync({ mediaTypes: ImagePicker.MediaTypeOptions.Images, aspect: [4, 3], allowsEditing: false, quality: 0.7, }); if (result.canceled) return; // 保存到相册,单独捕获错误避免影响主流程 try { await MediaLibrary.saveToLibraryAsync(result.assets[0].uri); } catch (saveErr) { console.log("保存相册失败:", saveErr); } setPhoto(result.assets[0].uri); setData({ ...data, [data?.value]: { title: data?.title, files: [result.assets[0].uri], }, }); } catch (err) { console.log("相机调用错误:", err); Alert.alert("相机打开失败", JSON.stringify(err)); } }; return ( <View style={styles.inputContainer}> <Text>{title}</Text> <TouchableOpacity onPress={getImageOfCamera}> {!!photo ? ( <View style={[globalStyles.marginTopMini, globalStyles.alignItemsCenter]} > <Image source={{ uri: photo }} style={{ width: 120, height: 100, borderRadius: 6 }} /> </View> ) : ( <View> <Feather name="camera" size={24} /> <Text> Abrir câmera</Text> </View> )} </TouchableOpacity> </View> ); }); export default ImageInspect;
关键修改点
- 添加组件挂载时的
initCameraPermissions函数,主动触发权限检查完成ActivityResultLauncher注册 - 优化错误处理逻辑,避免用户取消拍照时执行后续代码
- 拆分相册保存的错误捕获,避免单个环节失败导致整个相机流程中断
内容的提问来源于stack exchange,提问作者Victor Ferri
相关产品推荐
相关产品推荐

