Android Camera2 API连续拍摄第三张图片存相册延迟问题
问题分析与解决方案
核心问题定位
连续拍摄三张图片时第三张保存耗时过长,主要由以下几个关键问题导致:
1. 每次拍照重复创建CameraCaptureSession与ImageReader
代码在每次拍照时都会新建CameraCaptureSession和ImageReader,但未及时释放旧的Session资源。相机硬件资源有限,连续创建Session会引发资源竞争,第三次拍摄时相机服务需要等待资源释放,进而拉长整体耗时。
2. Location回调阻塞图像处理流程
在ImageReader.OnImageAvailableListener中实时调用userLocation.getLastLocation(),如果位置请求是异步排队执行,第三次拍摄时可能因前两次的位置请求未完成导致阻塞,拖慢保存流程。
3. 图像数据处理与文件操作的线程冲突
将ByteBuffer拷贝到byte[]的操作在相机后台线程执行,但后续的文件写入、EXIF修改、MediaScanner扫描未分离到独立IO线程,会占用相机处理线程,导致延迟累积。
具体修复方案
方案1:复用CameraCaptureSession,避免重复创建
不要每次拍照都新建CaptureSession,而是在预览初始化时创建一次,拍照时直接复用该Session提交拍摄请求。
代码修改示例:
// 成员变量保存Session private CameraCaptureSession mCaptureSession; // 初始化预览时创建Session(原createCameraPreview方法内) private void createCameraPreview() { List<Surface> outputSurfaces = new ArrayList<>(); outputSurfaces.add(new Surface(binding.textUreView.getSurfaceTexture())); cameraDevice.createCaptureSession(outputSurfaces, new CameraCaptureSession.StateCallback() { @Override public void onConfigured(@NonNull CameraCaptureSession session) { mCaptureSession = session; // 启动预览请求 try { CaptureRequest previewRequest = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW) .addTarget(outputSurfaces.get(0)) .build(); mCaptureSession.setRepeatingRequest(previewRequest, null, mBackgroundHandler); } catch (CameraAccessException e) { showErrorDialog(getString(R.string.error_title_camera), getString(R.string.camera_access_exception, e.getMessage())); } } @Override public void onConfigureFailed(@NonNull CameraCaptureSession session) { session.close(); } }, mBackgroundHandler); } // 拍照方法直接复用已有Session private void takePicture() { try { CameraManager manager = (CameraManager) getActivity().getSystemService(Context.CAMERA_SERVICE); CameraCharacteristics characteristics = manager.getCameraCharacteristics(cameraDevice.getId()); Size[] jpegSizes = Objects.requireNonNull(characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)).getOutputSizes(ImageFormat.JPEG); int width = jpegSizes[0].getWidth(); int height = jpegSizes[0].getHeight(); ImageReader reader = ImageReader.newInstance(width, height, ImageFormat.JPEG, 2); // 配置CaptureRequest.Builder逻辑保持不变... if (mCaptureSession != null && mCaptureSession.isOpen()) { // 添加ImageReader的Surface到Session临时目标 mCaptureSession.capture(captureBuilder.build(), captureListener, mBackgroundHandler); } } catch (CameraAccessException e) { throw new RuntimeException(e); } }
方案2:提前缓存Location,避免拍照时实时请求
在页面初始化或预览启动时预获取位置并缓存,拍照时直接使用缓存数据,避免位置请求的异步排队延迟。
代码修改示例:
// 成员变量缓存位置 private Location mCachedLocation; @Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); // 预获取位置 userLocation.getLastLocation(location -> { mCachedLocation = location; }); } // 图像回调中使用缓存位置 ImageReader.OnImageAvailableListener readerListener = imageReaderListener -> { try (Image image = imageReaderListener.acquireLatestImage()) { if (image == null) { showUserMessage(getString(R.string.message_image_capture_failed)); return; } ByteBuffer buffer = image.getPlanes()[0].getBuffer(); byte[] bytes = new byte[buffer.capacity()]; buffer.get(bytes); // 优先使用缓存位置,为空时再重新获取 if (mCachedLocation != null) { processImageSave(bytes, mCachedLocation.getLatitude(), mCachedLocation.getLongitude()); } else { userLocation.getLastLocation(location -> { mCachedLocation = location; processImageSave(bytes, location.getLatitude(), location.getLongitude()); }); } } catch (Exception e) { showUserMessage(getString(R.string.message_image_processing_error)); cameraViewModel.setCameraException(e); } };
方案3:将文件操作移至独立IO线程
创建专门的IO线程池处理文件写入、EXIF修改和MediaScanner扫描,避免阻塞相机后台线程。
代码修改示例:
// 单例IO线程池 private ExecutorService mIoExecutor = Executors.newSingleThreadExecutor(); private void processImageSave(byte[] bytes, double latitude, double longitude) { mIoExecutor.submit(() -> { try { String savedFileName = saveImageWithLocation(bytes, latitude, longitude); // 回到主线程更新UI和ViewModel getActivity().runOnUiThread(() -> { cameraViewModel.setImageFinalName(savedFileName); showUserMessage(getString(R.string.message_image_save_successful)); }); } catch (IOException e) { getActivity().runOnUiThread(() -> { showUserMessage(getString(R.string.message_image_save_failed)); cameraViewModel.setCameraException(e); }); } }); } // 原saveImageWithLocation方法保持不变,确保在IO线程执行
方案4:优化ImageReader资源配置
- 将
ImageReader.newInstance的maxImages参数设置为2(足够处理连续拍摄,避免资源浪费) - 确保每次
acquireLatestImage()后通过try-with-resources自动关闭Image实例(你的代码已实现,无需修改)
额外优化建议
- 避免在
saveImageWithLocation中频繁更新ViewModel的setImageFileName和setFileDirectory,仅在必要时执行 - 检查
ensureDirectoryExists()方法,确保目录创建是一次性操作,避免重复判断IO状态 - Android Q及以上版本,使用
MediaStoreAPI替代MediaScannerConnection,更高效地将图片插入系统相册,避免扫描延迟
内容的提问来源于stack exchange,提问作者Luai
相关产品推荐
相关产品推荐

