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
相关产品推荐
相关产品推荐

