
1. 项目概述为什么二维码扫描在 React Native 里不是“装个包就能用”的事最近帮某高校实验室做一款校园服务类 App 的跨平台版本核心功能之一是让学生用手机扫课表二维码快速跳转选课页同时支持从相册里挑一张带二维码的截图识别——听起来就是调个react-native-camera或react-native-qrcode-scanner就完事我最初也这么想。结果从环境初始化到真机调试前后踩了整整 11 个坑光是 iOS 的Info.plist权限配置就改了 5 轮安卓端更是在build.gradle里反复注释/反注释android.useAndroidXtrue和android.enableJetifiertrue这两行折腾了两天才跑通第一帧预览画面。这不是个别现象而是 React Native 生态里一个典型的“表面简单、底层复杂”的高频场景二维码扫描不是纯 JS 功能它强依赖原生摄像头模块、图像处理管线、权限生命周期管理、以及不同 Android 版本对 Camera2 API 的兼容策略。你写的 JS 代码只占整个链路的 30%剩下 70% 是桥接层、原生模块、系统级配置和运行时状态同步。所以这篇日记不叫“React Native 二维码集成指南”而叫“踩坑日记”——因为所有能搜到的教程都默认你已搞定 Xcode 工程配置、Gradle 插件版本对齐、NDK 构建缓存清理这些“前置隐形门槛”。而现实是90% 的失败案例根本没走到“写 JS 逻辑”这一步卡死在npm run ios报RCTCameraManager not found或者安卓模拟器直接黑屏无预览。本文记录的是从零开始、不跳过任何环节、逐个击破的真实路径所有命令、配置、参数值均来自实测iOS 17.5 Xcode 15.4Android 13 Pixel 4a 真机不假设你有原生开发经验但要求你愿意打开终端、编辑.plist文件、看懂 Gradle 错误堆栈。适合正在被扫码功能卡住进度的中初级 RN 开发者也适合想搞清“JS 桥接原生到底发生了什么”的进阶同学。2. 整体设计思路与方案选型为什么放弃“一键式”库坚持手搭双模块架构2.1 三种主流方案的硬伤对比刚接手时我快速扫了一遍社区方案发现主流就三类纯 JS 解码库如jsqr优点是零原生依赖npm install后直接import缺点是必须先拿到图片数据base64 或 Uint8Array意味着你要自己实现拍照/选图 → 裁剪 → 传给 JS → 解码的全链路且jsqr对模糊、倾斜、低对比度二维码识别率极低实测 10 张相册截图仅识别出 3 张完全不可商用。封装完善的“全家桶”库如react-native-qrcode-scanner优点是 API 简洁QRCodeScanner onRead{...} /一行搞定缺点是过度封装导致定制性为零——比如你想在扫描框上加个“点击放大镜图标切换闪光灯”它不提供torchMode控制接口再比如你想限制只扫 QR Code 不扫条形码它底层用的AVCaptureMetadataOutput默认开启全部类型关掉就得改原生代码。分层解耦的手动集成本方案采用即用react-native-vision-camera替代已废弃的react-native-camera负责摄像头预览与帧捕获用react-native-vision-camera-code-scanner作为其插件处理实时解码相册识别则用react-native-image-picker拿到图片后通过react-native-community/blur做轻量级预处理再交由react-native-vision-camera-code-scanner的静态解码 API 处理。这个方案工作量最大但换来的是① 预览性能可控VisionCamera 支持frameProcessor低延迟处理② 权限粒度精确可单独请求相机/相册/定位权限③ 错误可追溯帧捕获失败、解码超时、图片格式不支持等错误均有明确回调。提示选择手动集成不是为了炫技而是因为业务方明确要求“扫描失败时显示具体原因是光线太暗还是图片模糊或是非标准二维码”——这种颗粒度的需求任何黑盒库都无法满足。2.2 为什么 VisionCamera 是当前唯一可靠选择2024 年中react-native-camera已停止维护其衍生库react-native-camera-kit在 Android 12 上频繁崩溃而react-native-vision-camera由同一作者Mikael Rågård主导核心优势在于真正的零拷贝帧传递VisionCamera 的frameProcessor可直接在原生线程处理Frame对象无需将 YUV 数据序列化为 JS 对象再传回避免了 300ms 的序列化开销。实测在 iPhone 13 上1080p 预览下解码延迟稳定在 80~120ms而旧方案普遍在 350ms 以上。声明式权限管理它内置useCameraDevices()Hook自动根据设备能力返回back/front/ultra-wide等设备对象并附带isMultiCamSupported、supportsFlash等布尔属性让你在 JS 层就能做“如果设备支持闪光灯则渲染开关按钮”这类逻辑不用写原生判断。渐进式降级策略当用户拒绝相机权限时它不会直接报错而是返回undefined设备你可以在 UI 上优雅提示“请前往设置开启相机权限”而不是让 App 白屏崩溃。注意VisionCamera 要求 React Native ≥ 0.71且 iOS 必须启用 Swift 支持use_frameworks!。如果你的项目还在 RN 0.68升级是必选项别试图降级适配——我试过用 patch-package 强行兼容结果在 M1 Mac 上构建失败 7 次最终花半天升级 RN反而节省了三天排错时间。2.3 相册识别为何不能复用摄像头解码器这里有个关键认知误区很多人以为“同一个解码库既能在摄像头流里用也能在静态图里用”。但 VisionCamera 的codeScanner插件设计上scanCodes函数是绑定在Frame实例上的而Frame对象只能由 VisionCamera 的useCameraDevice创建的摄像头生成。相册图片是ImagePickerResponse返回的uri字符串两者数据源完全不同。强行把 uri 转成 Frame 会触发Invalid frame type错误。正确做法是相册流程走独立路径——用react-native-vision-camera-code-scanner提供的静态解码函数scanCodesFromImage它接受uri或base64内部会自动加载图片、转换色彩空间、调用底层解码器。这个函数不依赖摄像头实例是真正意义上的“离线解码”。3. 核心细节解析与实操要点从环境初始化到首帧预览的完整链路3.1 iOS 端Xcode 配置的 5 个致命细节很多教程只说“在Info.plist里加NSCameraUsageDescription”但实际远不止于此。我在 Xcode 15.4 中为让 VisionCamera 正常工作必须完成以下 5 步缺一不可启用 Swift 支持并指定版本在ios/Podfile顶部添加# 必须放在 platform :ios, 12.0 之后 use_frameworks! $RNFirebaseAsStaticFramework true然后在ios/YourApp.xcworkspace中右键YourApp→New File→ 选择Swift File→ 保存时 Xcode 会弹窗问“是否创建 Bridging Header”点Create。这一步强制 Xcode 初始化 Swift 环境否则 VisionCamera 的 Swift 模块无法链接。Info.plist 权限描述必须完整除了基础的相机和相册VisionCamera 还可能用到麦克风用于某些 AR 场景的音频同步所以Info.plist至少要包含keyNSCameraUsageDescription/key string需要访问相机以扫描二维码/string keyNSPhotoLibraryUsageDescription/key string需要访问相册以识别图片中的二维码/string keyNSMicrophoneUsageDescription/key string需要访问麦克风以支持增强现实功能/string !-- 如果你的 App 有定位需求还需加 -- keyNSLocationWhenInUseUsageDescription/key string需要定位以提供基于位置的服务/stringLink Binary With Libraries 必须手动添加打开YourApp→Build Phases→Link Binary With Libraries点击号搜索并添加libRNVisionCamera.alibRNVisionCameraCodeScanner.alibcabi.tbd这是 C 异常处理库缺失会导致std::bad_cast崩溃Build Settings 中的两个关键开关Enable Bitcode设为NOVisionCamera 官方明确要求设为 YES 会导致 Archive 失败Always Embed Swift Standard Libraries设为YES确保 Swift 运行时随 App 打包Podfile 中的 post_install 钩子这是最容易被忽略的一步。VisionCamera 依赖CocoaPods的use_modular_headers!但 RN 默认不启用。在ios/Podfile底部添加post_install do |installer| react_native_post_install(installer) installer.pods_project.targets.each do |target| target.build_configurations.each do |config| config.build_settings[SWIFT_VERSION] 5.9 config.build_settings[ENABLE_BITCODE] NO end end end然后执行cd ios pod deintegrate pod install --repo-update。不执行pod deintegrate旧的.xcworkspace缓存会干扰新配置。实操心得每次修改Info.plist或Build Settings后必须Clean Build FolderXcode 菜单栏Product → Clean Build Folder再CmdR运行。我曾因忘记 Clean导致修改了 3 小时的权限描述始终不生效最后发现 Xcode 在用旧的缓存二进制。3.2 Android 端Gradle 配置与权限的 4 层校验安卓的坑比 iOS 更隐蔽因为它允许你“看似成功运行”但实际摄像头打不开。我总结出必须校验的 4 层第一层Gradle 版本对齐android/build.gradle中// 必须匹配 RN 官方推荐版本 ext { buildToolsVersion 34.0.0 minSdkVersion 21 compileSdkVersion 34 targetSdkVersion 34 ndkVersion 25.1.8937393 }注意ndkVersion必须精确到小数点后VisionCamera 3.15.0 要求25.1.8937393用25.2会报NDK version mismatch。第二层app/build.gradle 的 kotlin 和 compose 配置android { namespace com.yourapp compileSdk 34 defaultConfig { applicationId com.yourapp minSdk 21 targetSdk 34 versionName 1.0 versionCode 1 // 必须添加这一行否则 VisionCamera 的 Kotlin 扩展无法识别 multiDexEnabled true } // 必须启用 Java 11否则编译报错 compileOptions { sourceCompatibility JavaVersion.VERSION_11 targetCompatibility JavaVersion.VERSION_11 } kotlinOptions { jvmTarget 11 } }第三层AndroidManifest.xml 的权限与硬件声明uses-permission android:nameandroid.permission.CAMERA / uses-permission android:nameandroid.permission.READ_EXTERNAL_STORAGE / uses-permission android:nameandroid.permission.WRITE_EXTERNAL_STORAGE / !-- Android 13 需要这个 -- uses-permission android:nameandroid.permission.READ_MEDIA_IMAGES / !-- 声明硬件需求避免 Google Play 分发给不支持设备 -- uses-feature android:nameandroid.hardware.camera android:requiredtrue / uses-feature android:nameandroid.hardware.camera.autofocus android:requiredfalse /第四层动态权限请求的时机陷阱不要在组件useEffect里直接request(PERMISSIONS.ANDROID.CAMERA)VisionCamera 的useCameraDeviceHook 内部会检查权限如果权限未授予它会静默返回undefinedUI 不会报错但预览黑屏。正确做法是const device useCameraDevice(back); const [hasPermission, setHasPermission] useState(false); useEffect(() { (async () { const status await requestCameraPermissions(); setHasPermission(status granted); })(); }, []); if (device null || !hasPermission) { return View style{styles.loading}Text请求权限中.../Text/View; }即先请求权限再检查设备是否存在。顺序颠倒就是黑屏。3.3 JS 层帧处理与解码的性能临界点控制即使原生配置全对JS 层写法不当也会导致卡顿。VisionCamera 的frameProcessor是在原生线程运行的但它的输出如codes数组仍需序列化到 JS 线程。如果每帧都触发console.log(codes)序列化开销会让 FPS 掉到 10 以下。我的优化策略是解码频率控制不每帧都扫而是用useRef记录上一次成功解码时间间隔 500ms 再扫下一次const lastScanTime useRef(0); const frameProcessor useFrameProcessor((frame) { worklet; if (Date.now() - lastScanTime.current 500) return; const codes scanCodes(frame, [qr, ean-13]); if (codes.length 0) { lastScanTime.current Date.now(); runOnJS(onCodeScanned)(codes[0].value); // 仅在 JS 线程触发回调 } }, []);解码类型精简scanCodes(frame, [qr])比scanCodes(frame, [qr, ean-13, code-128])快 40%因为解码器只需加载 QR Code 的特征模型。业务只要扫二维码就别加其他类型。相册图片预处理ImagePicker返回的图片可能是 HEICiOS或 WebP安卓scanCodesFromImage不支持。必须先用react-native-image-resizer转成 JPEGconst resized await resizeImage({ path: response.assets[0].uri, maxWidth: 1200, maxHeight: 1200, format: JPEG, quality: 80, }); const result await scanCodesFromImage(resized.uri, [qr]);注意resizeImage的quality参数不是越高越好。实测quality: 100时1200px 图片体积达 3MB解码耗时 1.2squality: 80时体积 800KB耗时 320ms且识别率无差异。这是典型的“过犹不及”。4. 实操过程与核心环节实现从创建页面到双通道识别的完整代码4.1 页面结构与状态管理设计我们不使用全局状态管理如 Redux因为扫码是原子操作状态极简。核心状态只有 4 个cameraPermissionGranted: 布尔值相机权限是否已授cameraDevice: VisionCamera 的CameraDevice对象或nullscanning: 布尔值是否处于扫描中用于禁用按钮lastResult: 字符串最后识别到的二维码内容页面结构采用“Tab 切换”模式上半部分是摄像头预览区下半部分是操作按钮区底部 Tab 切换“实时扫描”和“相册识别”// ScannerScreen.tsx export default function ScannerScreen() { const [activeTab, setActiveTab] useStatecamera | gallery(camera); const [cameraPermissionGranted, setCameraPermissionGranted] useState(false); const [cameraDevice, setCameraDevice] useStateCameraDevice | null(null); const [scanning, setScanning] useState(false); const [lastResult, setLastResult] useStatestring | null(null); // 权限请求与设备获取 useEffect(() { const initCamera async () { const status await requestCameraPermissions(); setCameraPermissionGranted(status granted); if (status granted) { const device await Camera.getCameraDevice(back); setCameraDevice(device); } }; initCamera(); }, []); return ( SafeAreaView style{styles.container} {/* 预览区 */} {activeTab camera ? ( CameraPreview cameraDevice{cameraDevice} permissionGranted{cameraPermissionGranted} onCodeScanned{setLastResult} / ) : ( GalleryPreview onCodeScanned{setLastResult} / )} {/* 操作区 */} View style{styles.actionBar} TouchableOpacity style{[styles.tabButton, activeTab camera styles.activeTab]} onPress{() setActiveTab(camera)} Text style{styles.tabText}实时扫描/Text /TouchableOpacity TouchableOpacity style{[styles.tabButton, activeTab gallery styles.activeTab]} onPress{() setActiveTab(gallery)} Text style{styles.tabText}相册识别/Text /TouchableOpacity /View {/* 结果展示 */} {lastResult ( View style{styles.resultBox} Text style{styles.resultText}识别成功/Text Text style{styles.resultValue} numberOfLines{2}{lastResult}/Text /View )} /SafeAreaView ); }4.2 实时扫描模块CameraPreview 组件详解CameraPreview是核心它封装了 VisionCamera 的所有复杂性// components/CameraPreview.tsx import { Camera, useCameraDevice, useCameraFormat } from react-native-vision-camera; import { useFrameProcessor, scanCodes } from react-native-vision-camera-code-scanner; import { runOnJS } from react-native-reanimated; type CameraPreviewProps { cameraDevice: CameraDevice | null; permissionGranted: boolean; onCodeScanned: (value: string) void; }; export function CameraPreview({ cameraDevice, permissionGranted, onCodeScanned }: CameraPreviewProps) { const [torch, setTorch] useStateoff | on(off); const [isScanning, setIsScanning] useState(true); const lastScanTime useRef(0); // 自动选择最优格式分辨率/帧率平衡 const format useCameraFormat(cameraDevice, [ { videoResolution: 1080p, fps: 30 }, { videoResolution: 720p, fps: 60 }, ]); // 帧处理器worklet 环境运行 const frameProcessor useFrameProcessor((frame) { worklet; if (!isScanning || Date.now() - lastScanTime.current 500) return; // 仅扫描 QR Code提升速度 const codes scanCodes(frame, [qr]); if (codes.length 0) { lastScanTime.current Date.now(); runOnJS(onCodeScanned)(codes[0].value); // 扫到后暂停 1 秒避免连续触发 setIsScanning(false); setTimeout(() setIsScanning(true), 1000); } }, [isScanning]); if (!permissionGranted || cameraDevice null) { return ( View style{styles.permissionPlaceholder} Text请授予相机权限以开始扫描/Text /View ); } return ( View style{styles.cameraContainer} Camera style{styles.camera} device{cameraDevice} isActive{true} torch{torch} format{format} frameProcessor{frameProcessor} frameProcessorFps{5} // 限制帧处理频率省电 / {/* 闪光灯开关 */} TouchableOpacity style{styles.torchButton} onPress{() setTorch(prev prev off ? on : off)} Icon name{torch on ? flash : flash-off} size{24} color#fff / /TouchableOpacity {/* 扫描框蒙层 */} View style{styles.scanOverlay} View style{styles.scanLine} / /View /View ); } const styles StyleSheet.create({ cameraContainer: { flex: 1, backgroundColor: black, }, camera: { ...StyleSheet.absoluteFillObject, }, torchButton: { position: absolute, top: 40, right: 20, width: 50, height: 50, borderRadius: 25, backgroundColor: rgba(0,0,0,0.5), justifyContent: center, alignItems: center, }, scanOverlay: { ...StyleSheet.absoluteFillObject, justifyContent: center, alignItems: center, }, scanLine: { width: 80%, height: 2, backgroundColor: #00FF00, borderRadius: 1, }, });关键点解析frameProcessorFps{5}强制 VisionCamera 每秒最多向帧处理器推送 5 帧而非默认的 30 帧。因为人眼识别二维码不需要高帧率降低此值可显著减少 CPU 占用实测 iPhone 13 上 CPU 从 45% 降至 18%。setTimeout(() setIsScanning(true), 1000)扫到码后暂停 1 秒防止用户还没看清结果界面就又触发下一次识别。这是用户体验的黄金细节。scanOverlay蒙层纯 CSS 实现不涉及原生避免影响性能。绿色扫描线用height: 2而非borderTopWidth因为后者在某些安卓机型上会有渲染抖动。4.3 相册识别模块GalleryPreview 组件实现GalleryPreview专注于“选图→预处理→解码”三步// components/GalleryPreview.tsx import { launchImageLibrary } from react-native-image-picker; import { resizeImage } from react-native-image-resizer; import { scanCodesFromImage } from react-native-vision-camera-code-scanner; type GalleryPreviewProps { onCodeScanned: (value: string) void; }; export function GalleryPreview({ onCodeScanned }: GalleryPreviewProps) { const pickImage async () { const options: ImagePickerOptions { mediaType: photo, includeBase64: false, quality: 0.8, maxHeight: 1200, maxWidth: 1200, }; launchImageLibrary(options, async (response) { if (response.didCancel || response.errorCode) { console.warn(ImagePicker cancelled or error:, response.errorCode); return; } if (response.assets response.assets.length 0) { try { // 第一步转 JPEG解决 HEIC/WebP 兼容问题 const resized await resizeImage({ path: response.assets[0].uri, maxWidth: 1200, maxHeight: 1200, format: JPEG, quality: 80, }); // 第二步解码 const result await scanCodesFromImage(resized.uri, [qr]); if (result.length 0) { onCodeScanned(result[0].value); } else { Alert.alert(未识别到二维码, 请确保图片清晰二维码完整可见); } } catch (error) { console.error(Scan failed:, error); Alert.alert(识别失败, 图片格式不支持或二维码质量过低); } } }); }; return ( View style{styles.container} TouchableOpacity style{styles.pickButton} onPress{pickImage} Text style{styles.pickButtonText}从相册选择图片/Text /TouchableOpacity Text style{styles.hintText}支持 JPG、PNG 格式建议图片大小不超过 2MB/Text /View ); } const styles StyleSheet.create({ container: { flex: 1, justifyContent: center, alignItems: center, padding: 20, }, pickButton: { backgroundColor: #007AFF, paddingHorizontal: 40, paddingVertical: 16, borderRadius: 8, }, pickButtonText: { color: white, fontSize: 16, fontWeight: 600, }, hintText: { marginTop: 20, color: #666, fontSize: 14, textAlign: center, }, });关键点解析resizeImage的quality: 80是经过 12 次实测得出的最优值低于 70图片模糊导致识别率暴跌高于 85文件体积增大但识别率无提升纯属浪费带宽。scanCodesFromImage返回的是PromiseCode[]不是Promisestring。必须检查result.length 0否则result[0].value会报undefined错误。错误处理分两级launchImageLibrary的errorCode如permission、camera_unavailable和scanCodesFromImage的catch如invalid_image_format、no_qr_code_found。分别给出针对性提示而不是笼统的“识别失败”。5. 常见问题与排查技巧实录11 个真实坑位及解决方案5.1 iOS 真机黑屏Xcode 中的 “No Video Data” 错误现象App 在 iPhone 上启动后摄像头区域纯黑Xcode 控制台报Error: No video data received from camera。排查路径检查Info.plist是否漏了NSCameraUsageDescription检查Build Settings → Linking → Other Linker Flags是否包含-lc检查Build Phases → Link Binary With Libraries是否漏了libcabi.tbd终极检查在 Xcode 中Product → Scheme → Edit Scheme → Run → Arguments查看Environment Variables是否为空。如果存在OS_ACTIVITY_MODE disable删除它——这是 RN CLI 自动生成的会屏蔽 VisionCamera 的日志输出导致你无法看到真正的错误。解决方案按顺序执行上述 4 步90% 的黑屏问题可解决。若仍不行在Camera组件上加onInitialized{() console.log(Camera initialized)}确认是否进入初始化回调。5.2 安卓模拟器无预览Camera2 API 不可用现象Android Studio 模拟器如 Pixel 4 API 34启动后预览区灰色无任何报错。原因大多数模拟器默认禁用 Camera2 API而 VisionCamera 强制使用它。验证方法在模拟器设置中Settings → System → Developer options → Camera app查看是否为Camera2。若为Legacy则不支持。解决方案方案 A推荐换用真机调试成本最低方案 B在模拟器AVD Manager中编辑你的虚拟设备 →Show Advanced Settings→Camera→ 将Front/Back Camera都设为Webcam0即调用电脑摄像头方案 C在app/src/main/java/com/yourapp/MainActivity.java中于onCreate方法内添加if (Build.VERSION.SDK_INT Build.VERSION_CODES.LOLLIPOP) { getWindow().setFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON, WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); }这能强制唤醒摄像头硬件。5.3 扫描灵敏度低总提示“请靠近二维码”现象明明二维码清晰距离 30cm却一直提示“请靠近”或需手动对焦多次才识别。根因分析VisionCamera 默认关闭自动对焦AF尤其在低端安卓机上镜头无法自动调节焦距。解决方案在Camera组件中显式启用 AFCamera ... enableFocusDetection{true} // 启用焦点检测 autoFocus{on} // 强制开启自动对焦 /同时在useFrameProcessor中加入对焦辅助逻辑const frameProcessor useFrameProcessor((frame) { worklet; // 当检测到二维码区域时触发一次对焦 const codes scanCodes(frame, [qr]); if (codes.length 0 codes[0].bounds.width 100) { // bounds.width 100 表示二维码在画面中已足够大可尝试对焦 runOnJS(() { if (cameraRef.current) { cameraRef.current.focus(); } }); } }, []);5.4 相册识别失败HEIC 格式图片报错现象iOS 用户从相册选图后scanCodesFromImage报Error: Unsupported image format。原因iPhone 默认用 HEIC 格式存储照片而react-native-vision-camera-code-scanner的静态解码器不支持 HEIC。解决方案必须在resizeImage前先用react-native-image-picker的includeBase64: true选项获取 base64再用react-native-image-resizer的base64: true选项转 JPEG// 替代方案绕过 uri用 base64 流式处理 const options: ImagePickerOptions { mediaType: photo, includeBase64: true, // 关键 quality: 0.8, }; launchImageLibrary(options, async (response) { if (response.assets response.assets[0].base64) { const resized await resizeImage({ base64: response.assets[0].base64, maxWidth: 1200, maxHeight: 1200, format: JPEG, quality: 80, }); const result await scanCodesFromImage(resized.path, [qr]); // 注意这里是 resized.path } });5.5 多次扫描后内存泄漏JS 对象堆积现象连续扫描 20 次后App 卡顿内存占用飙升至 1.2GBXcode 的 Memory Graph 显示大量Frame对象未释放。原因useFrameProcessor创建的 worklet 函数持有对onCodeScanned回调的引用而该回调若来自父组件如setLastResult会形成闭包阻止 GC。解决方案使用useCallback包裹回调并在frameProcessor中用runOnJS时传入稳定引用// 在父组件中 const handleCodeScanned useCallback((value: string) { setLastResult(value); }, []); // 在 CameraPreview 中 Camera frameProcessor{frameProcessor} // frameProcessor 内部用 runOnJS(handleCodeScanned) 而非 runOnJS(onCodeScanned) /5.6 闪光灯无效iOS 上点击无反应现象torch{on}设置后闪光灯不亮。原因VisionCamera 的torch属性只在device.supportsFlash true时生效而useCameraDevice(back)返回的设备对象其supportsFlash属性需在device ! null后才能读取。解决方案在Camera组件中动态判断Camera torch{device?.supportsFlash ? torch : off} ... /并在 UI 上禁用闪光灯按钮{device?.supportsFlash ? ( TouchableOpacity onPress{() setTorch(...)} / ) : ( View style{styles.torchDisabled} / )}5.7 Android 13 相册权限崩溃READ_MEDIA_IMAGES 缺失现象Android 13 设备上点击“相
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。