API 33以下版本相机无法工作问题排查求助
问题描述
实现带音频录制的相机功能,使用Dexter做权限校验,视频可保存到设备文件夹。该功能在API32及以上设备正常运行,但API33以下设备无法启动,调试发现Dexter的权限校验逻辑未正常执行。
原始代码片段
AndroidManifest.xml
<uses-feature android:name="android.hardware.camera" android:required="false" /> <uses-feature android:name="android.hardware.camera.any" android:required="false" /> <uses-feature android:name="android.hardware.camera.autofocus" android:required="false" /> <uses-feature android:name="android.hardware.camera.flash" android:required="false" /> <uses-permission android:name="android.permission.CAMERA" /> <uses-permission android:name="android.permission.RECORD_AUDIO" /> <uses-feature android:name="android.permission.MANAGE_EXTERNAL_STORAGE" android:required="false" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="28" /> <uses-permission android:name="android.permission.READ_MEDIA_AUDIO" /> <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.POST_NOTIFICATIONS"/> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.WAKE_LOCK" /> <uses-permission android:name="android.permission.VIBRATE" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.READ_MEDIA_IMAGES" /> <uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
Fragment.kt(关键代码)
private fun initCamera() { cameraExecutor = Executors.newSingleThreadExecutor() requestCameraPermission() } private fun requestCameraPermission() { Dexter.withContext(requireContext()) .withPermissions( Manifest.permission.RECORD_AUDIO, Manifest.permission.CAMERA ).withListener(object : MultiplePermissionsListener { override fun onPermissionsChecked(p0: MultiplePermissionsReport?) { p0?.let { if (it.areAllPermissionsGranted()) { startCamera(CameraSelector.DEFAULT_BACK_CAMERA) } } } override fun onPermissionRationaleShouldBeShown( p0: MutableList<PermissionRequest>?, p1: PermissionToken? ) { p1?.continuePermissionRequest() } }).check() } private fun startCamera(cameraSelector: CameraSelector) { val cameraProviderFuture = ProcessCameraProvider.getInstance(requireContext()) val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get() cameraProviderFuture.addListener({ // Preview配置 val preview = Preview.Builder() .build() .also { binding.previewViewTalkerDuelCreateNewDuelTakeVideo.implementationMode = PreviewView.ImplementationMode.COMPATIBLE it.setSurfaceProvider( binding.previewViewTalkerDuelCreateNewDuelTakeVideo.surfaceProvider ) } val recorder = Recorder.Builder() .setQualitySelector( QualitySelector.from( Quality.HIGHEST, FallbackStrategy.higherQualityOrLowerThan(Quality.SD) ) ) .build() videoCapture = VideoCapture.withOutput(recorder) try { cameraProvider.unbindAll() cameraProvider.bindToLifecycle( this, cameraSelector, preview, videoCapture ) } catch (exc: Exception) { exc.printStackTrace() } }, ContextCompat.getMainExecutor(requireContext())) }
Dexter版本
implementation 'com.karumi:dexter:6.2.3'
核心问题分析
主线程阻塞导致Dexter权限流程中断
startCamera方法中直接调用cameraProviderFuture.get()同步获取ProcessCameraProvider,会阻塞主线程(startCamera由权限回调触发,运行在主线程),导致主线程卡死,Dexter无法弹出权限弹窗、触发回调,表现为权限校验逻辑“未正常执行”。AndroidManifest配置错误
将MANAGE_EXTERNAL_STORAGE权限错误声明为<uses-feature>,该权限属于特殊权限,正确声明应为<uses-permission>,且Dexter不支持自动处理此类权限。存储权限覆盖不全(视频保存环节)
API29-32(Android10-12)设备使用Scoped Storage,若需保存视频到公共目录,当前Manifest的权限配置不足,但这是视频保存问题,并非相机无法启动的直接原因。
修复步骤
1. 修复startCamera的线程阻塞问题
移除同步get()调用,将cameraProvider的获取放到addListener回调中,避免阻塞主线程:
private fun startCamera(cameraSelector: CameraSelector) { val cameraProviderFuture = ProcessCameraProvider.getInstance(requireContext()) cameraProviderFuture.addListener({ // 在回调中安全获取cameraProvider,此时Future已完成 val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get() // Preview配置 val preview = Preview.Builder() .build() .also { binding.previewViewTalkerDuelCreateNewDuelTakeVideo.implementationMode = PreviewView.ImplementationMode.COMPATIBLE it.setSurfaceProvider( binding.previewViewTalkerDuelCreateNewDuelTakeVideo.surfaceProvider ) } // Recorder配置 val recorder = Recorder.Builder() .setQualitySelector( QualitySelector.from( Quality.HIGHEST, FallbackStrategy.higherQualityOrLowerThan(Quality.SD) ) ) .build() videoCapture = VideoCapture.withOutput(recorder) try { // 解绑旧用例再绑定新用例 cameraProvider.unbindAll() cameraProvider.bindToLifecycle( this, cameraSelector, preview, videoCapture ) } catch (exc: Exception) { exc.printStackTrace() } }, ContextCompat.getMainExecutor(requireContext())) }
2. 修正AndroidManifest的权限声明错误
将错误的<uses-feature>改为<uses-permission>,并添加版本限制:
<!-- 替换错误的uses-feature声明 --> <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" android:minSdkVersion="30" />
3. 确保Fragment生命周期正确调用initCamera
在onViewCreated中调用initCamera(),确保Fragment已关联有效Context,Dexter能正常初始化权限请求:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) initCamera() }
4. 补充API29-32的存储权限处理(可选,针对视频保存)
若需在API29-32设备保存视频到公共目录,需引导用户手动开启MANAGE_EXTERNAL_STORAGE权限:
private fun checkManageStoragePermission() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && !Environment.isExternalStorageManager()) { val intent = Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION) intent.data = Uri.parse("package:${requireContext().packageName}") startActivity(intent) } }
内容的提问来源于stack exchange,提问作者Ahmet Yılmaz

