使用 CameraX 框架实现双摄并发预览
目录一、 核心优势二、 执行流程详解三、 异常处理机制补充建议源码这段代码展示了使用CameraX框架实现双摄并发预览同时开启前置和后置摄像头的高级写法。相比于传统的 Camera2 API这段代码不仅结构清晰还充分利用了 CameraX 的现代特性。以下是对这段代码的原理、优势及执行流程的详细解析一、 核心优势防卡顿与异步安全使用addListener配合getMainExecutor避免了在主线程直接调用阻塞方法如.get()确保了 UI 界面的流畅度。优雅处理硬件限制通过UseCaseGroup用例组将多个相机需求打包。如果设备硬件不支持双摄并发系统会抛出明确的异常而不是出现难以排查的黑屏或崩溃。生命周期自动管理通过bindToLifecycle相机的开启、暂停和释放与 Activity/Fragment 的生命周期自动绑定极大降低了内存泄漏的风险。自动解绑防冲突在绑定新配置前调用unbindAll()清理了之前的旧状态防止多次点击导致相机资源被重复占用。二、 执行流程详解1. 异步获取相机提供者ListenableFutureProcessCameraProvider cameraProviderFuture ProcessCameraProvider.getInstance(this); cameraProviderFuture.addListener(() - { ... }, ContextCompat.getMainExecutor(this));原理ProcessCameraProvider是 CameraX 的核心管理类。getInstance是一个异步操作返回一个ListenableFuture。执行当底层相机服务初始化完毕后才会触发addListener中的回调并在主线程MainExecutor中执行后续逻辑。2. 清理旧状态cameraProvider.unbindAll();原理解绑当前绑定到该生命周期上的所有相机用例。执行防止在用户反复切换页面或重新初始化相机时因为旧相机未释放而导致CameraInUseException。3. 构建独立的预览用例Preview previewBack new Preview.Builder().build(); previewBack.setSurfaceProvider(previewView1.getSurfaceProvider()); // ... 前置同理原理CameraX 采用“用例UseCase”模式。每个Preview都是一个独立的用例它负责将相机的图像流输出到 UI 控件PreviewView上。4. 核心特性创建用例组 (UseCaseGroup)UseCaseGroup useCaseGroup new UseCaseGroup.Builder() .addUseCase(previewBack) .addUseCase(previewFront) .build();原理这是实现多路预览的关键。它将前置和后置两个Preview用例打包。CameraX 底层的协调器会尝试在硬件层同时满足这两个用例的需求。5. 统一绑定生命周期cameraProvider.bindToLifecycle( this, CameraSelector.DEFAULT_BACK_CAMERA, // 主相机选择器 useCaseGroup );原理将打包好的用例组与当前组件的生命周期绑定。注意这里传入的CameraSelector.DEFAULT_BACK_CAMERA是作为“主相机”的参考配置。在UseCaseGroup内部每个用例其实可以独立指定自己的CameraSelector如果代码中没有显式指定CameraX 会尝试根据上下文或默认规则进行分配。三、 异常处理机制} catch (Exception e) { Toast.makeText(this, 启动失败(可能硬件不支持双摄): e.getMessage(), Toast.LENGTH_SHORT).show(); }原理并非所有 Android 手机的硬件和底层 HAL 都支持前后摄像头同时开启。执行如果底层资源不足或不支持并发bindToLifecycle会抛出异常如CameraInfoUnavailableException。这段代码捕获了异常并给用户友好的提示保证了程序的健壮性。补充建议在实际开发中如果要在UseCaseGroup中明确指定前置和后置通常会在构建Preview时通过setCameraSelector明确区分例如Preview previewBack new Preview.Builder() .setCameraSelector(CameraSelector.DEFAULT_BACK_CAMERA) .build();这样可以确保 CameraX 准确地将对应的流分配给正确的物理摄像头。源码public class MultiCameraActivity extends AppCompatActivity { private PreviewView previewView1, previewView2; // 1. 【最新权限写法】使用 ActivityResultLauncher 替代老旧的 onRequestPermissionsResult private final ActivityResultLauncherString requestPermissionLauncher registerForActivityResult(new ActivityResultContracts.RequestPermission(), isGranted - { if (isGranted) { startCameraPreview(); } else { Toast.makeText(this, 未授予相机权限无法使用预览功能, Toast.LENGTH_SHORT).show(); Intent intent new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); intent.setData(Uri.parse(package: getPackageName())); startActivity(intent); finish(); } }); Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_camera); previewView1 findViewById(R.id.previewView1); previewView2 findViewById(R.id.previewView2); // 2. 检查权限状态 if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) PackageManager.PERMISSION_GRANTED) { startCameraPreview(); } else { requestPermissionLauncher.launch(Manifest.permission.CAMERA); } } /** * 【最新相机启动写法】 * 使用 UseCaseGroup 统一管理多路预览防止多摄并发时的资源冲突。 */ private void startCameraPreview() { ListenableFutureProcessCameraProvider cameraProviderFuture ProcessCameraProvider.getInstance(this); // 【防卡顿优化】使用 addListener 异步获取避免 .get() 阻塞主线程 cameraProviderFuture.addListener(() - { try { ProcessCameraProvider cameraProvider cameraProviderFuture.get(); cameraProvider.unbindAll(); // 清理旧状态 // 1. 构建后置摄像头预览 Preview previewBack new Preview.Builder().build(); previewBack.setSurfaceProvider(previewView1.getSurfaceProvider()); // 2. 构建前置摄像头预览 Preview previewFront new Preview.Builder().build(); previewFront.setSurfaceProvider(previewView2.getSurfaceProvider()); // 3. 【核心新特性】创建 UseCaseGroup (用例组) // 将多个相机用例打包在一起CameraX 会自动协调硬件资源 // 如果硬件不支持双开系统会抛出异常而不会导致莫名其妙的黑屏。 UseCaseGroup useCaseGroup new UseCaseGroup.Builder() .addUseCase(previewBack) .addUseCase(previewFront) .build(); // 4. 统一绑定到生命周期 cameraProvider.bindToLifecycle( this, CameraSelector.DEFAULT_BACK_CAMERA, // 主相机选择器 useCaseGroup ); Toast.makeText(this, 双摄预览启动成功, Toast.LENGTH_SHORT).show(); } catch (Exception e) { // 如果设备硬件不支持双摄并发会在这里捕获异常 Toast.makeText(this, 启动失败(可能硬件不支持双摄): e.getMessage(), Toast.LENGTH_SHORT).show(); } }, ContextCompat.getMainExecutor(this)); } Override protected void onDestroy() { super.onDestroy(); } }