使用CameraX在PreviewView中拍照时相机冻结重启异常排查
问题描述
- 开发环境:Android 12(API level 31),基于CameraX API开发
- 异常现象:PreviewView可正常显示相机实时画面,但点击拍照按钮后,相机画面出现冻结并重启
- 错误日志:
/ImageCapture: Send image capture request [current, pending] = [0, 1] D/ImageCapture: issueTakePicture D/Camera2CameraImpl: {Camera@bcf4c18[id=0]} Issue capture request D/CaptureSession: Issuing capture request. D/EGL_emulation: app_time_stats: avg=20.36ms min=1.77ms max=477.02ms count=39 E/CameraCaptureSession: Session 0: Exception while stopping repeating: android.hardware.camera2.CameraAccessException: CAMERA_ERROR (3): cancelRequest:507: Camera 0: Error clearing streaming request: Function not implemented (-38) at android.hardware.camera2.CameraManager.throwAsPublicException(CameraManager.java:1179) ...
代码实现
import android.Manifest; import android.content.pm.PackageManager; import android.os.Bundle; import android.view.View; import android.widget.Button; import android.widget.Toast; import androidx.appcompat.app.AppCompatActivity; import androidx.camera.core.Camera; import androidx.camera.core.CameraSelector; import androidx.camera.core.ImageCapture; import androidx.camera.core.ImageCaptureException; import androidx.camera.core.Preview; import androidx.camera.lifecycle.ProcessCameraProvider; import androidx.camera.view.PreviewView; import androidx.core.app.ActivityCompat; import androidx.core.content.ContextCompat; import com.google.common.util.concurrent.ListenableFuture; import java.io.File; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; public class MainActivity extends AppCompatActivity { private PreviewView viewFinder; private ProcessCameraProvider cameraProvider; private Camera camera; private ImageCapture imageCapture; private static final int CAMERA_PERMISSION_CODE = 1; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); viewFinder = findViewById(R.id.viewFinder); startCamera(); Button takePictureButton = findViewById(R.id.takePictureButton); takePictureButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { takePicture(); } }); } private void startCamera() { if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) { // 权限已授予,打开相机 } else { // 请求权限 ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA}, CAMERA_PERMISSION_CODE); } ListenableFuture<ProcessCameraProvider> cameraProviderFuture = ProcessCameraProvider.getInstance(this); cameraProviderFuture.addListener(() -> { try { // 获取可用的相机提供者 cameraProvider = cameraProviderFuture.get(); // 配置并启动相机预览 bindPreview(); } catch (ExecutionException | InterruptedException e) { // 处理错误 } }, ContextCompat.getMainExecutor(this)); } private void bindPreview() { imageCapture = new ImageCapture.Builder() .setTargetRotation(viewFinder.getDisplay().getRotation()) .build(); Preview preview = new Preview.Builder().build(); preview.setSurfaceProvider(viewFinder.getSurfaceProvider()); // 选择后置摄像头 CameraSelector cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA; // 绑定生命周期 camera = cameraProvider.bindToLifecycle(this, cameraSelector, preview, imageCapture); } private void takePicture() { Executor cameraExecutor = ContextCompat.getMainExecutor(this); ImageCapture.OutputFileOptions outputFileOptions = new ImageCapture.OutputFileOptions.Builder(new File(getExternalMediaDirs()[0], "photo.jpg")).build(); imageCapture.takePicture(outputFileOptions, cameraExecutor, new ImageCapture.OnImageSavedCallback() { @Override public void onImageSaved(ImageCapture.OutputFileResults outputFileResults) { System.out.println("保存成功"); Toast.makeText(MainActivity.this, "照片已保存", Toast.LENGTH_SHORT).show(); } @Override public void onError(ImageCaptureException error) { System.out.println("保存失败"); Toast.makeText(MainActivity.this, "保存照片出错", Toast.LENGTH_SHORT).show(); System.out.println(error); } } ); } }
问题排查与解决方案
问题根源分析
错误日志中的CAMERA_ERROR (3)和Function not implemented (-38),结合代码细节,核心问题包括:
- 权限处理不完整:相机权限申请通过后,未重新触发相机初始化流程
- 主线程处理拍照操作:主线程执行IO或相机捕获逻辑,易阻塞相机会话导致异常终止
- ImageCapture配置缺失:未设置适配的捕获模式,与预览会话存在兼容性冲突
- 文件路径未做空安全校验:
getExternalMediaDirs()[0]可能为空,引发潜在异常
修复步骤
- 完善权限回调逻辑
重写onRequestPermissionsResult方法,在权限授予后重新初始化相机:
@Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); if (requestCode == CAMERA_PERMISSION_CODE) { if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { startCamera(); } else { Toast.makeText(this, "相机权限未授予,无法使用拍照功能", Toast.LENGTH_SHORT).show(); } } }
- 使用独立线程Executor处理拍照
避免主线程阻塞,创建单线程Executor:
private Executor cameraExecutor = Executors.newSingleThreadExecutor();
替换takePicture()中的ContextCompat.getMainExecutor(this)为该Executor
- 优化ImageCapture配置
添加捕获模式设置,提升兼容性并降低延迟:
imageCapture = new ImageCapture.Builder() .setTargetRotation(viewFinder.getDisplay().getRotation()) .setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY) .build();
- 文件路径空安全处理
创建文件前校验存储目录可用性:
private void takePicture() { File mediaDir = getExternalMediaDirs()[0]; if (mediaDir == null || !mediaDir.exists()) { Toast.makeText(this, "存储目录不可用", Toast.LENGTH_SHORT).show(); return; } File photoFile = new File(mediaDir, "photo.jpg"); ImageCapture.OutputFileOptions outputFileOptions = new ImageCapture.OutputFileOptions.Builder(photoFile).build(); // 后续拍照逻辑不变 }
- 升级CameraX依赖版本
确保使用适配Android 12的稳定版CameraX,在build.gradle中配置:
dependencies { def camerax_version = "1.2.3" implementation "androidx.camera:camera-core:$camerax_version" implementation "androidx.camera:camera-camera2:$camerax_version" implementation "androidx.camera:camera-lifecycle:$camerax_version" implementation "androidx.camera:camera-view:$camerax_version" }
内容的提问来源于stack exchange,提问作者Daniel
相关产品推荐
相关产品推荐

