You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android CameraX多次拍照后预览黑屏问题排查求助

问题分析与修复方案

核心问题原因

你的代码存在以下关键问题,导致相机资源未正确释放、内存泄漏,最终引发预览黑屏甚至退出Activity后屏幕仍黑屏的现象:

  • CameraProvider未在Activity销毁时解绑:cameraProvider是startCamera方法内的局部变量,Activity销毁时无法调用unbindAll()释放相机资源,导致系统相机被持续占用。
  • 重复设置监听器与观察者:每次调用startCamera都会重复设置触摸监听器、点击事件监听器、TorchState观察者,造成内存泄漏和多次回调。
  • OrientationEventListener未禁用:Activity销毁时未禁用方向监听器,该监听器持有Context引用,引发内存泄漏,同时可能持续干扰相机状态。
  • startCamera可能被多次调用:拍摄照片后调用的vistaPrevia()若再次触发startCamera,会导致相机重复绑定/解绑,资源清理不彻底。

修复步骤与代码调整

1. 将核心对象改为Activity成员变量

在Activity类中声明以下成员变量,确保能在生命周期方法中访问并清理:

private ProcessCameraProvider cameraProvider;
private Camera camera;
private OrientationEventListener orientationEventListener;
private ScaleGestureDetector scaleGestureDetector;
private Observer<Integer> torchStateObserver;

2. 重构startCamera方法,避免重复创建监听器

private void startCamera(boolean enableTorch) {
    final ListenableFuture<ProcessCameraProvider> cameraProviderFuture = ProcessCameraProvider.getInstance(this);

    cameraProviderFuture.addListener(() -> {
        try {
            cameraProvider = cameraProviderFuture.get();

            // 先解绑所有已绑定的用例,避免重复绑定
            if (cameraProvider != null) {
                cameraProvider.unbindAll();
            }

            Preview preview = new Preview.Builder()
                    .setTargetAspectRatio(AspectRatio.RATIO_4_3)
                    .build();

            CameraSelector cameraSelector = new CameraSelector.Builder()
                    .requireLensFacing(CameraSelector.LENS_FACING_BACK)
                    .build();

            ImageAnalysis imageAnalysis = new ImageAnalysis.Builder().build();

            ImageCapture imageCapture = new ImageCapture.Builder()
                    .setTargetRotation(getWindowManager().getDefaultDisplay().getRotation())
                    .setTargetAspectRatio(AspectRatio.RATIO_4_3)
                    .setCaptureMode(ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY)
                    .build();

            preview.setSurfaceProvider(mPreviewView.createSurfaceProvider());

            // 绑定生命周期与相机用例
            camera = cameraProvider.bindToLifecycle(this, cameraSelector, preview, imageAnalysis, imageCapture);
            camera.getCameraControl().enableTorch(enableTorch);
            CameraControl cameraControl = camera.getCameraControl();

            // 初始化缩放监听器(仅创建一次)
            if (scaleGestureDetector == null) {
                ScaleGestureDetector.OnScaleGestureListener listener = new ScaleGestureDetector.SimpleOnScaleGestureListener() {
                    @Override
                    public boolean onScale(ScaleGestureDetector detector) {
                        if (camera == null) return true;
                        float currentZoomRatio = camera.getCameraInfo().getZoomState().getValue().getZoomRatio();
                        float delta = detector.getScaleFactor();
                        cameraControl.setZoomRatio(currentZoomRatio * delta);
                        return true;
                    }
                };
                scaleGestureDetector = new ScaleGestureDetector(this, listener);
            }

            // 设置触摸监听器(确保仅设置一次)
            mPreviewView.setOnTouchListener((view, event) -> {
                scaleGestureDetector.onTouchEvent(event);
                switch (event.getAction()) {
                    case MotionEvent.ACTION_DOWN:
                        return true;
                    case MotionEvent.ACTION_UP:
                        if (camera == null) return true;
                        MeteringPoint point = mPreviewView.createMeteringPointFactory(cameraSelector).createPoint(event.getX(), event.getY());
                        FocusMeteringAction action = new FocusMeteringAction.Builder(point).build();
                        camera.getCameraControl().startFocusAndMetering(action);
                        return true;
                    default:
                        return false;
                }
            });

            // 初始化方向监听器(仅创建一次)
            if (orientationEventListener == null) {
                orientationEventListener = new OrientationEventListener(this) {
                    @Override
                    public void onOrientationChanged(int orientation) {
                        int rotation;
                        if (orientation >= 45 && orientation < 135) {
                            rotation = Surface.ROTATION_270;
                        } else if (orientation >= 135 && orientation < 225) {
                            rotation = Surface.ROTATION_180;
                        } else if (orientation >= 225 && orientation < 315) {
                            rotation = Surface.ROTATION_90;
                        } else {
                            rotation = Surface.ROTATION_0;
                        }
                        if (imageCapture != null) {
                            imageCapture.setTargetRotation(rotation);
                        }
                    }
                };
            }
            orientationEventListener.enable();

            // 拍照按钮点击事件(仅设置一次)
            captureImage.setOnClickListener(v -> {
                if (imageCapture == null) return;
                File file = new File(archivo_foto);
                ImageCapture.OutputFileOptions outputFileOptions = new ImageCapture.OutputFileOptions.Builder(file).build();
                imageCapture.takePicture(outputFileOptions, executor, new ImageCapture.OnImageSavedCallback() {
                    @Override
                    public void onImageSaved(@NonNull ImageCapture.OutputFileResults outputFileResults) {
                        try {
                            if (ComprasUtils.debeRotar(MiCamaraActivity.this)) {
                                getRotacionConf(archivo_foto);
                            } else {
                                getRotacion2(archivo_foto);
                            }
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                        vistaPrevia();
                    }

                    @Override
                    public void onError(@NonNull ImageCaptureException error) {
                        error.printStackTrace();
                    }
                });
            });

            // 闪光灯按钮点击事件(仅设置一次)
            btnflash.setOnClickListener(v -> {
                if (camera == null || !camera.getCameraInfo().hasFlashUnit()) return;
                cameraControl.enableTorch(camera.getCameraInfo().getTorchState().getValue() == TorchState.OFF);
            });

            // Torch状态观察者(仅创建并观察一次)
            if (torchStateObserver == null) {
                torchStateObserver = integer -> {
                    if (integer == TorchState.OFF) {
                        btnflash.setImageResource(R.drawable.flash_on);
                    } else {
                        btnflash.setImageResource(R.drawable.flash_off);
                    }
                };
            }
            // 先移除旧的观察,再添加新的(避免重复观察)
            camera.getCameraInfo().getTorchState().removeObserver(torchStateObserver);
            camera.getCameraInfo().getTorchState().observe(this, torchStateObserver);

        } catch (ExecutionException | InterruptedException e) {
            e.printStackTrace();
            milog.grabarError(TAG + " " + e.getMessage());
        }
    }, ContextCompat.getMainExecutor(this));
}

3. 在Activity生命周期方法中清理资源

重写Activity的onDestroy方法,彻底释放相机资源和监听器:

@Override
protected void onDestroy() {
    super.onDestroy();
    // 解绑所有相机用例
    if (cameraProvider != null) {
        cameraProvider.unbindAll();
    }
    // 禁用方向监听器
    if (orientationEventListener != null) {
        orientationEventListener.disable();
    }
    // 移除Torch状态观察者
    if (camera != null && torchStateObserver != null) {
        camera.getCameraInfo().getTorchState().removeObserver(torchStateObserver);
    }
    // 清空引用,帮助GC回收
    camera = null;
    cameraProvider = null;
    orientationEventListener = null;
    scaleGestureDetector = null;
    torchStateObserver = null;
}

4. 检查vistaPrevia()方法

确保vistaPrevia()不会重复调用startCamera,如果需要重启相机,先确保之前的资源已被清理,或者直接调用cameraProvider.unbindAll()再重新绑定,而不是再次调用整个startCamera方法。

额外注意事项

  • 避免在短时间内多次调用startCamera,每次调用前确保已解绑所有相机用例。
  • 确保executor是单例或生命周期绑定的线程池,避免创建过多线程导致资源消耗。
  • 检查getRotacionConf和getRotacion2方法是否存在文件操作阻塞主线程或内存泄漏的情况。

内容的提问来源于stack exchange,提问作者Marisol vega

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.08 23:26:01