Android Camera2 API深度解析:从架构原理到实战应用

发布时间:2026/7/31 7:06:36
Android Camera2 API深度解析:从架构原理到实战应用 1. 项目概述为什么我们需要深入理解Camera2 API如果你是一名Android应用开发者并且你的应用需要与摄像头打交道那么你大概率已经听说过甚至“深受其苦”于Camera2 API。从Android 5.0API Level 21开始Google引入了全新的Camera2 API来取代老旧的Camera API。这个变化对于很多习惯了简单几行代码就能调用摄像头的开发者来说无异于一次认知上的“降维打击”。Camera1时代我们调用Camera.open()设置一些参数然后startPreview()一个简单的预览画面就出来了。但Camera2完全不同它引入了一套基于管道Pipeline和会话Session的异步、事件驱动的复杂架构。那么为什么Google要“自找麻烦”设计出如此复杂的Camera2 API呢核心原因在于老旧的Camera API已经无法满足现代智能手机摄像头硬件飞速发展的需求。如今的手机摄像头早已不是简单的“一个镜头、一个传感器”。多摄像头系统广角、超广角、长焦、高动态范围HDR、人像模式、夜景模式、高速连拍、4K/8K视频录制……这些复杂的功能背后是硬件厂商在图像信号处理器ISP、传感器、镜头模组上投入的巨大研发。Camera1 API那种“一刀切”的简单模型严重限制了硬件能力的发挥也使得不同厂商的设备表现差异巨大。Camera2 API的设计哲学是将摄像头硬件抽象为一个“数据生产工厂”。应用你写的代码作为这个工厂的“总调度师”可以精细地控制生产流水线Pipeline的每一个环节从原材料的获取传感器捕获原始数据到不同车间的加工例如一个流水线产出预览图另一个流水线产出高分辨率照片再到最终产品的输出格式和目的地。这套模型赋予了开发者前所未有的控制力能够充分发挥硬件的潜力实现更专业、更复杂的拍摄效果。但同时这也意味着开发者需要承担更多的责任理解架构、管理状态、处理异步回调、协调资源。可以说Camera2 API是一把双刃剑用好了能打造出媲美原生相机应用的体验用不好则会让应用崩溃、卡顿、耗电如流水。接下来我将以一个“工厂调度师”的视角带你彻底拆解Camera2 API的架构和核心工作流程。无论你是正在为项目集成一个自定义相机还是单纯想理解这套复杂系统背后的逻辑这篇文章都将提供从理论到实操的完整路径。我们会避开官方文档那种冰冷的叙述用实际编码中会遇到的问题和解决方案来串联起整个知识体系。2. Camera2 API核心架构深度拆解要驾驭Camera2首先必须建立起正确的“心智模型”。你不能把它看作是一组简单的函数调用而应该视为一个需要你精心编排的、由多个独立组件构成的系统。2.1 核心组件与角色扮演Camera2 API的核心围绕着几个关键类展开每个类都扮演着“工厂”中的特定角色CameraManager工厂经理 这是你与设备上所有摄像头硬件交互的入口点。它就像一个工厂的总经理办公室你通过它来查询工厂有哪些生产线摄像头以及每条生产线的基本能力。它的核心方法是getCameraIdList()获取所有摄像头ID和openCamera()申请打开并连接一条特定的生产线。注意openCamera()是一个异步操作。你不能在主线程UI线程上调用它因为它可能耗时较长需要初始化硬件会导致应用无响应ANR。必须在后台线程执行。CameraDevice具体生产线 成功调用openCamera()后你会通过回调得到一个CameraDevice实例。这个对象就代表了你刚刚申请到的那个具体的摄像头硬件。它是所有后续操作的基础。你可以把它理解为一条已经通电、待命的生产线但流水线具体怎么跑生产什么还没定。CameraCharacteristics生产线说明书 在打开摄像头之前或之后你都可以通过CameraManager.getCameraCharacteristics(cameraId)来获取这个摄像头的“能力说明书”。这是一个不可变的对象里面包含了海量的信息硬件能力是否支持自动对焦AF、自动曝光AE、光学防抖OIS等。输出格式支持支持哪些图像尺寸SCALER_STREAM_CONFIGURATION_MAP例如哪些分辨率支持YUV_420_888格式哪些支持JPEG直接输出。传感器信息朝向、物理尺寸等。3AAF/AE/AWB模式范围支持哪些对焦模式固定、自动、连续等。 在编写任何拍摄逻辑前仔细阅读这份“说明书”是避免踩坑的关键。例如你不能强行要求一个没有自动对焦模块的摄像头去执行连续自动对焦。CaptureRequest生产订单 这是Camera2 API的灵魂。它定义了一次“捕获”操作的具体要求。你可以把它想象成一张发给生产线的“订单单”。订单模板通过CameraDevice.createCaptureRequest(int templateType)创建。系统预定义了几个模板如TEMPLATE_PREVIEW用于预览、TEMPLATE_STILL_CAPTURE用于高质量拍照、TEMPLATE_RECORD用于录像。模板会预先填充一些合理的默认参数。订单细节你可以对CaptureRequest.Builder对象设置大量的参数来定制这张订单。例如CaptureRequest.CONTROL_AF_MODE: 设置对焦模式如CONTROL_AF_MODE_CONTINUOUS_PICTURE用于拍照连续对焦。CaptureRequest.JPEG_QUALITY: 设置JPEG图片质量。CaptureRequest.SCALER_CROP_REGION: 设置数字变焦区域。最重要的是你需要为订单指定“收货地址”即addTarget(Surface)。一个Surface代表一个数据输出的目的地如预览的SurfaceView/TextureView拍照的ImageReader。Surface货物目的地/仓库 这是图像数据的最终去向。在Android系统中Surface代表一个可以绘制图像数据的缓冲区生产者端。常见的Surface来源有SurfaceView/TextureView用于实时预览将图像直接显示在屏幕上。ImageReader一个极其重要的组件。它允许你以指定的格式如YUV_420_888, JPEG和尺寸在内存中获取图像数据从而进行后续处理如人脸识别、滤镜、保存到文件。MediaRecorder用于视频录制将图像数据编码为视频文件。 一个CaptureRequest可以同时向多个Surface发送数据这就是实现“一拍多得”例如同时生成预览图和全尺寸JPEG的基础。CameraCaptureSession生产流水线与调度中心 这是将上述所有部分串联起来的“调度中心”。在你配置好一个或多个Surface目的地后你需要通过CameraDevice.createCaptureSession()来创建一个会话。这个会话会建立一条从摄像头传感器到这些Surface的固定数据流水线。会话配置创建会话时你需要传入一个ListSurface即所有你希望数据流向的目的地。会话一旦创建这条流水线的拓扑结构就固定了。之后你可以通过这个会话来提交不同的CaptureRequest订单但订单的“收货地址”必须是创建会话时指定的那些Surface之一。会话的生命周期创建 - 就绪 - 运行提交请求- 关闭。关闭会话是一个昂贵的操作应尽量避免在频繁拍照/预览中反复创建和关闭。2.2 状态机理解摄像头的“工作心情”CameraDevice和CameraCaptureSession都有自己的状态机。不理解它们你的应用就会经常遇到“IllegalStateException”非法状态异常。CameraDevice状态机CLOSED关闭。初始和最终状态。OPENING正在打开。调用openCamera()后进入。OPENED已打开。可以创建CaptureSession。DISCONNECTED设备意外断开如另一个高优先级应用抢占了摄像头。你必须清理资源并关闭设备。ERROR发生错误。通常需要关闭设备。你需要监听CameraDevice.StateCallback来响应这些状态变化。重中之重在onDisconnected()和onError()回调中必须关闭CameraDevice并释放所有相关资源。CameraCaptureSession状态机CONFIGURING正在配置。调用createCaptureSession后进入。READY配置成功就绪。可以在onConfigured回调中开始提交捕获请求。CLOSED已关闭。FAILED配置失败。会话不可用必须创建新的。监听CameraCaptureSession.StateCallback特别是onConfigured()这是你开始发送CaptureRequest的绿灯信号。实操心得很多初学者遇到的“Session has been closed”或“Device is not open”错误根源就在于没有理清这些异步回调的顺序。一个稳健的流程是在CameraDevice.StateCallback.onOpened()中创建CaptureSession然后在CameraCaptureSession.StateCallback.onConfigured()中开始发送预览请求。确保所有耗时操作打开设备、创建会话都在后台线程进行但状态回调的处理要注意线程上下文更新UI需切回主线程。3. Camera2 完整工作流程与实操实现理论讲完了我们来看一个从零开始实现相机预览和拍照的完整流程。我会把代码拆解成块并解释每一步的意图和注意事项。3.1 第一步权限申请与相机选择在AndroidManifest.xml中声明权限uses-permission android:nameandroid.permission.CAMERA / !-- 如果需保存文件还需要 -- uses-feature android:nameandroid.hardware.camera android:requiredtrue /在Android 6.0 (API 23)及以上需要在运行时动态申请CAMERA权限。同时如果需要写入存储还需要申请WRITE_EXTERNAL_STORAGE权限。选择相机时通常后置主摄的ID是“0”前置是“1”但这不是绝对的。更可靠的做法是遍历CameraManager.getCameraIdList()并通过CameraCharacteristics检查LENS_FACING属性来区分前后置。private String getBackCameraId(CameraManager manager) throws CameraAccessException { for (String cameraId : manager.getCameraIdList()) { CameraCharacteristics characteristics manager.getCameraCharacteristics(cameraId); Integer facing characteristics.get(CameraCharacteristics.LENS_FACING); if (facing ! null facing CameraCharacteristics.LENS_FACING_BACK) { // 进一步检查能力例如是否支持自动对焦 int[] afModes characteristics.get(CameraCharacteristics.CONTROL_AF_AVAILABLE_MODES); if (afModes ! null afModes.length 0) { return cameraId; // 找到一个可用的后置摄像头 } } } return null; }3.2 第二步打开相机与配置输出目的地假设我们有两个输出目的地一个用于预览的TextureView一个用于捕获高分辨率JPEG照片的ImageReader。private CameraDevice cameraDevice; private CameraCaptureSession captureSession; private ImageReader imageReader; private Size previewSize; // 根据TextureView尺寸和相机支持的分辨率计算得出 private Size captureSize; // 选择相机支持的最大JPEG分辨率 // 1. 创建用于拍照的ImageReader imageReader ImageReader.newInstance(captureSize.getWidth(), captureSize.getHeight(), ImageFormat.JPEG, /*maxImages*/ 2); // 缓冲区数量2通常足够 imageReader.setOnImageAvailableListener(onImageAvailableListener, backgroundHandler); // 2. 获取TextureView的Surface SurfaceTexture texture textureView.getSurfaceTexture(); texture.setDefaultBufferSize(previewSize.getWidth(), previewSize.getHeight()); Surface previewSurface new Surface(texture); // 3. 打开相机 CameraManager manager (CameraManager) context.getSystemService(Context.CAMERA_SERVICE); String cameraId getBackCameraId(manager); // 使用上面定义的方法 manager.openCamera(cameraId, new CameraDevice.StateCallback() { Override public void onOpened(NonNull CameraDevice camera) { cameraDevice camera; // 相机已打开接下来创建CaptureSession createCameraPreviewSession(); } Override public void onDisconnected(NonNull CameraDevice camera) { camera.close(); cameraDevice null; } Override public void onError(NonNull CameraDevice camera, int error) { camera.close(); cameraDevice null; } }, backgroundHandler); // 注意传入后台线程的Handler关键点解析ImageReader的maxImages参数它设置了可以同时存在于队列中的最大图像数量。如果处理速度跟不上图像生产速度队列满了之后旧的图像会被丢弃。对于拍照设置为2一个正在处理一个在队列通常足够。对于高帧率预览流可能需要更大。SurfaceTexture的setDefaultBufferSize必须调用这告诉SurfaceTexture底层缓冲区应该分配多大必须与你选择的预览分辨率一致否则预览会拉伸或变形。openCamera的回调onOpened是成功信号onDisconnected和onError是失败/异常信号必须在其中进行资源清理。3.3 第三步创建捕获会话并开始预览在onOpened回调中我们创建会话。private void createCameraPreviewSession() { try { Surface previewSurface ... // 从TextureView获取 Surface jpegSurface imageReader.getSurface(); // 准备一个用于预览的CaptureRequest.Builder CaptureRequest.Builder previewRequestBuilder cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); previewRequestBuilder.addTarget(previewSurface); // 预览流输出到TextureView // 创建会话传入所有要使用的Surface cameraDevice.createCaptureSession( Arrays.asList(previewSurface, jpegSurface), // 这里把拍照的Surface也加进去为后续拍照做准备 new CameraCaptureSession.StateCallback() { Override public void onConfigured(NonNull CameraCaptureSession session) { captureSession session; // 会话就绪开始连续发送预览请求 try { // 设置连续自动对焦模式适合预览 previewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE); // 设置自动曝光模式 previewRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH); CaptureRequest previewRequest previewRequestBuilder.build(); // setRepeatingRequest 会持续发送请求形成预览流 captureSession.setRepeatingRequest(previewRequest, null, // 可选的CaptureCallback用于监听3A状态 backgroundHandler); } catch (CameraAccessException e) { e.printStackTrace(); } } Override public void onConfigureFailed(NonNull CameraCaptureSession session) { showToast(创建会话失败); } }, backgroundHandler); } catch (CameraAccessException e) { e.printStackTrace(); } }核心操作解析createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW)使用预览模板它会自动配置一些适合实时预览的低延迟、高帧率参数。addTarget(Surface)将预览的Surface添加到请求中意味着这个请求产生的图像数据会流向这里。createCaptureSession(ListSurface, ...)这是建立数据流水线的关键调用。传入的Surface列表定义了本次会话所有可能的数据出口。一旦创建不能动态增删。setRepeatingRequest(CaptureRequest, ...)这个方法会循环不断地向摄像头提交同一个或同一系列请求从而形成连续的预览画面流。这是预览功能的核心。3.4 第四步实现拍照功能拍照本质上就是通过同一个CaptureSession提交一个不同于预览请求的新CaptureRequest。private void takePicture() { if (cameraDevice null || captureSession null) { return; } try { // 1. 创建一个用于拍照的CaptureRequest使用STILL_CAPTURE模板 CaptureRequest.Builder captureBuilder cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE); captureBuilder.addTarget(imageReader.getSurface()); // 输出到ImageReader // 2. 配置拍照专用参数 // 使用与预览相同的对焦和曝光设置或者可以锁定它们 captureBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE); // 设置JPEG方向根据设备旋转传感器信息 int rotation getWindowManager().getDefaultDisplay().getRotation(); captureBuilder.set(CaptureRequest.JPEG_ORIENTATION, getOrientation(rotation)); // 设置JPEG质量 captureBuilder.set(CaptureRequest.JPEG_QUALITY, (byte) 95); // 3. 停止预览可选为了获得更稳定的图像特别是闪光灯场景 // captureSession.stopRepeating(); // 4. 提交单次捕获请求 captureSession.capture(captureBuilder.build(), new CameraCaptureSession.CaptureCallback() { Override public void onCaptureCompleted(NonNull CameraCaptureSession session, NonNull CaptureRequest request, NonNull TotalCaptureResult result) { // 拍照完成图像数据已经在ImageReader的队列里了。 showToast(拍照完成); // 恢复预览如果在第3步停止了 // startPreviewAgain(); } Override public void onCaptureFailed(NonNull CameraCaptureSession session, NonNull CaptureRequest request, NonNull CaptureFailure failure) { showToast(拍照失败: failure.getReason()); } }, backgroundHandler); } catch (CameraAccessException e) { e.printStackTrace(); } }拍照流程精讲创建独立请求使用TEMPLATE_STILL_CAPTURE模板它针对高画质静态图像进行了优化如可能启用降噪、使用更高的图像质量设置。指定目标addTarget(imageReader.getSurface())是关键。这意味着这张“订单”的货物JPEG图像将被送到ImageReader这个“仓库”而不是预览的SurfaceView。参数设置JPEG_ORIENTATION极其重要传感器方向是固定的但手机可以旋转。这个参数告诉摄像头硬件在生成JPEG时进行旋转保证最终照片的方向正确。你需要根据设备当前朝向和传感器方向来计算这个值。JPEG_QUALITY设置压缩质量1-100。停止预览在部分场景下特别是使用闪光灯时为了保证拍照瞬间的曝光稳定可以先调用captureSession.stopRepeating()停止预览流拍完后再恢复。这不是必须的但能提升一些场景下的成片率。提交请求使用capture()而非setRepeatingRequest()。这是一个单次请求执行完就结束。处理结果在CaptureCallback.onCaptureCompleted中你知道硬件已经处理完这次捕获。但图像数据本身是通过ImageReader的监听器异步送达的。3.5 第五步处理捕获的图像数据拍照请求完成后图像数据被送入ImageReader的队列。private ImageReader.OnImageAvailableListener onImageAvailableListener new ImageReader.OnImageAvailableListener() { Override public void onImageAvailable(ImageReader reader) { // 必须在后台线程处理因为可能涉及耗时的I/O操作 try (Image image reader.acquireNextImage()) { if (image ! null) { // 通常我们只关心JPEG格式 if (image.getFormat() ImageFormat.JPEG) { ByteBuffer buffer image.getPlanes()[0].getBuffer(); byte[] bytes new byte[buffer.remaining()]; buffer.get(bytes); // 现在bytes就是JPEG图片的字节数组 saveImageToFile(bytes); // 保存到文件 // 或者解码为Bitmap: Bitmap bitmap BitmapFactory.decodeByteArray(bytes, 0, bytes.length); } } } catch (Exception e) { e.printStackTrace(); } } }; private void saveImageToFile(byte[] jpegData) { // 创建文件路径注意Android 10及以上作用域存储 String timeStamp new SimpleDateFormat(yyyyMMdd_HHmmss).format(new Date()); String fileName JPEG_ timeStamp .jpg; File outputFile new File(getExternalFilesDir(Environment.DIRECTORY_PICTURES), fileName); try (FileOutputStream output new FileOutputStream(outputFile)) { output.write(jpegData); runOnUiThread(() - showToast(照片已保存: outputFile.getAbsolutePath())); } catch (IOException e) { e.printStackTrace(); } }关键细节acquireNextImage()从队列中获取下一张可用的图像。这个方法必须在onImageAvailable被调用后尽快执行因为ImageReader的缓冲区是有限的。获取到的Image对象必须及时关闭使用try-with-resources语法释放底层缓冲区以供重用。Image.getPlanes()对于JPEG格式只有一个平面Plane 0其ByteBuffer包含了完整的JPEG文件数据。对于YUV等格式会有多个平面Y、U、V处理起来更复杂。线程安全onImageAvailable回调在创建ImageReader时传入的Handler所在的线程执行。务必确保在这个线程中进行文件保存等I/O操作避免阻塞UI线程。4. 高级话题、常见问题与避坑指南掌握了基本流程我们来看看那些容易让人“掉头发”的高级问题和坑点。4.1 多Surface输出与流配置Camera2的强大之处在于可以同时向多个Surface输出不同格式、不同尺寸的数据流。例如你可以同时向一个小的SurfaceView输出低分辨率、高帧率的预览流。向一个ImageReader输出中等分辨率的YUV流用于实时人脸检测。向另一个ImageReader输出全分辨率的JPEG流用于拍照。向MediaRecorder的Surface输出编码视频流。关键在于创建CaptureSession时传入所有需要的Surface。然后你可以为不同的CaptureRequest指定不同的目标组合。但有一个硬性限制所有Surface的尺寸组合必须在相机硬件支持的“流配置表”SCALER_STREAM_CONFIGURATION_MAP内。你可以通过CameraCharacteristics获取这个表并使用isOutputSupportedFor(Surface)或检查尺寸/格式组合是否在支持列表中来验证。避坑技巧在配置多流时一个常见的错误是请求了硬件不支持的尺寸或格式组合导致createCaptureSession失败。务必在代码中动态检查。一个简单的策略是优先确定对画质要求最高的那个流如全尺寸拍照流的尺寸然后根据StreamConfigurationMap.getOutputSizes()为其他流选择兼容的、更小的尺寸。4.2 对焦、测光与曝光3A控制Camera2提供了精细的3A控制。除了设置模式如CONTROL_AF_MODE_CONTINUOUS_PICTURE你还可以指定对焦/测光区域。// 假设用户在预览画面上点击了(x, y)点需要实现点击对焦 private void handleFocus(MeteringRectangle focusArea) { if (captureSession null) return; try { // 1. 取消任何正在进行的对焦可选 previewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, CameraMetadata.CONTROL_AF_TRIGGER_CANCEL); captureSession.capture(previewRequestBuilder.build(), null, backgroundHandler); // 2. 设置新的对焦区域 previewRequestBuilder.set(CaptureRequest.CONTROL_AF_REGIONS, new MeteringRectangle[]{focusArea}); // 同样可以设置测光区域 CONTROL_AE_REGIONS previewRequestBuilder.set(CaptureRequest.CONTROL_AE_REGIONS, new MeteringRectangle[]{focusArea}); // 3. 触发对焦 previewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, CameraMetadata.CONTROL_AF_TRIGGER_START); // 设置对焦模式为 AUTO previewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_AUTO); // 4. 提交这个对焦请求 captureSession.capture(previewRequestBuilder.build(), new CameraCaptureSession.CaptureCallback() { Override public void onCaptureCompleted(NonNull CameraCaptureSession session, NonNull CaptureRequest request, NonNull TotalCaptureResult result) { // 检查对焦状态 Integer afState result.get(CaptureResult.CONTROL_AF_STATE); if (afState CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED || afState CaptureResult.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED) { // 对焦已锁定成功或失败 // 可以恢复连续对焦模式 previewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, CameraMetadata.CONTROL_AF_TRIGGER_IDLE); previewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE); try { captureSession.setRepeatingRequest(previewRequestBuilder.build(), null, backgroundHandler); } catch (CameraAccessException e) { /* ... */ } } } }, backgroundHandler); } catch (CameraAccessException e) { e.printStackTrace(); } }注意事项MeteringRectangle的坐标是相对于传感器有效阵列的坐标系通常左上角为(0,0)右下角为SCALER_ACTIVE_ARRAY_SIZE定义的宽高而不是屏幕坐标。你需要将屏幕触摸点转换到传感器坐标系。对焦是一个异步过程需要通过监听CaptureResult.CONTROL_AF_STATE来获知对焦状态对焦中、已锁定、失败等。触发单次对焦CONTROL_AF_TRIGGER_START后通常需要在完成后将触发器设回IDLE并恢复之前的对焦模式。4.3 生命周期管理与资源释放这是Camera2开发中最容易导致内存泄漏和崩溃的部分。你必须严格遵守以下顺序关闭会话captureSession.close()关闭相机设备cameraDevice.close()释放Surface关闭ImageReader(imageReader.close())释放SurfaceTexture等。最佳实践是在Activity/Fragment的onPause()中执行关闭操作在onResume()中重新打开。但要注意onPause()/onResume()可能频繁调用如弹出对话框反复开关相机成本很高。一个更优的策略是使用LifecycleObserver在ON_STOP生命周期时关闭在ON_START时打开。一个典型的释放方法private void closeCamera() { if (captureSession ! null) { captureSession.close(); captureSession null; } if (cameraDevice ! null) { cameraDevice.close(); cameraDevice null; } if (imageReader ! null) { imageReader.close(); imageReader null; } }4.4 常见问题排查速查表问题现象可能原因排查步骤与解决方案打开相机失败1. 权限未授予。2. 相机被其他应用占用如系统相机。3.CameraManager.openCamera()在主线程调用导致ANR。1. 检查运行时权限。2. 捕获CameraAccessException其getReason()常为CAMERA_IN_USE。3. 确保在后台线程调用。创建CaptureSession失败1. 传入的Surface无效或已释放。2.Surface的尺寸/格式组合相机不支持。3. 相机设备已关闭。1. 检查Surface是否有效如TextureView是否已可用。2. 用StreamConfigurationMap验证尺寸/格式。3. 检查cameraDevice状态。预览画面拉伸或变形TextureView的SurfaceTexture未设置正确的缓冲区尺寸。在SurfaceTexture可用后调用setDefaultBufferSize(previewSize.width, previewSize.height)。拍照后图片方向错误未设置JPEG_ORIENTATION参数。根据设备当前旋转方向和传感器方向计算正确的JPEG_ORIENTATION值并设置到拍照请求中。拍照卡顿或延迟1.ImageReader的maxImages设置太小缓冲区不足。2. 保存图片等I/O操作阻塞了回调线程。3. 未及时关闭Image对象导致缓冲区无法重用。1. 适当增加maxImages如从2调到3。2. 确保onImageAvailable中的处理特别是文件保存在独立线程进行。3. 使用try-with-resources确保Image.close()被调用。同时预览和录像时崩溃多流配置超出了硬件能力。检查StreamConfigurationMap选择一组硬件支持的尺寸组合通常预览用小尺寸录像用支持的录像尺寸。IllegalStateException状态机错误。例如在会话关闭后仍提交请求或在设备断开后未正确清理。在所有CameraDevice.StateCallback和CameraCaptureSession.StateCallback的错误/断开回调中严格执行资源释放逻辑。在提交任何请求前检查cameraDevice和captureSession不为null且未关闭。5. 从Camera2到CameraX如何选择如果你被Camera2的复杂性吓到或者你的应用不需要那么极致的控制只是想快速、稳定地实现一个相机功能那么CameraX是你的绝佳选择。CameraX是Jetpack组件库的一部分它在Camera2的基础上提供了一套更简单、生命周期感知lifecycle-aware且向后兼容直到Android 5.0的API。CameraX的核心优势用例Use Case驱动你不需要直接管理CameraDevice、CaptureSession。而是声明你需要什么预览Preview、图片分析ImageAnalysis、拍照ImageCapture。CameraX帮你处理底层的兼容性和生命周期。自动设备选择无需自己遍历摄像头ID可以指定CameraSelector如DEFAULT_BACK_CAMERA。简化的生命周期绑定通过ProcessCameraProvider.bindToLifecycle()相机的开启和关闭会自动与你的Activity/Fragment生命周期同步。一致的API体验在不同厂商、不同Android版本的设备上行为更一致。Camera2 vs CameraX 选择建议选择Camera2你需要对图像处理管线进行极精细的控制例如自定义3A算法、直接处理RAW传感器数据、实现专业的手动模式、你的应用是专业摄影工具、或者你需要支持非常古老的、CameraX不支持的特定功能。选择CameraX你需要快速集成相机功能、你的应用是社交、扫描、简单拍摄类应用、你希望减少兼容性测试工作量、你不想处理复杂的生命周期和状态机。我个人在实际项目中的体会是对于90%的常规相机需求预览、拍照、录像、简单的图像分析CameraX已经完全够用且能节省大量的开发和维护成本。它封装了Camera2的复杂性让你能更专注于业务逻辑。但对于那10%需要触及硬件底层能力的场景深入理解Camera2的架构仍然是无可替代的。理解Camera2也能让你更好地理解CameraX背后发生了什么当遇到CameraX无法解决的极端情况时你才有能力深入底层去排查和解决。