
简介面向Android开发者的摄像头功能实现资料包围绕Camera1与Camera2两代API深入覆盖设备打开与参数配置、SurfaceView与TextureView预览显示、拍照与视频录制、运行时权限申请、画面旋转校正、多摄像头切换、闪光灯控制、面部检测等核心场景既适合刚接触相机开发的初学者按示例搭建基础功能也适合中高级开发者研究Camera2高级控制与YUV原始数据处理的实现思路。全套源码含470个文件以Java源码、XML资源配置、PNG界面图片及编译后的class文件为主另附Gradle工程配置和Git版本管理文件压缩包仅4.69MB整体结构清晰便于按模块检索与复用。已有6093人学习读者可重点参考其中权限请求与兼容处理、Camera2多路流并行、闪光灯与面部检测的触发逻辑直接移植到实际项目中作为自定义相机或图像采集模块的改造起点。1. 安卓摄像头开发到底在做什么先分清「调通」和「做全」很多人拿到一个「Android摄像头软件所有功能」这样的标题第一反应是打开 Android 官方文档把 Camera2 的十几个回调全抄一遍然后发现预览是黑的、拍照是绿的、录像没声音、插上耳机后对焦声和快门声全没了。这个标题表面上是「硬件能力的调用清单」实际上是一整套软件工程把光路变成数据把数据变成帧把帧变成文件再把文件变成用户能看懂的预览和相册内容。你能在这个标题下找到自己的位置通常意味着你正在做一个相机类 App、巡检记录工具、扫码或 AR 场景应用甚至是远程视频采集的前端。我先把话说透Android 摄像头软件功能不是一个 API 能覆盖完的。它至少横跨 CameraX 或 Camera2 的选型、预览与输出通道分离、对焦曝光策略、图像方向与 EXIF 修正、前后摄切换的兼容策略、录像与拍照的状态机切换、以及一颗亮灯闪光灯的控制时机。这篇文章不打算把每个功能都贴一遍官方示例而是按一条可落地的路径推进先讲清楚选型和工程骨架再逐个拆解成像控制、对焦曝光、方向修正、多摄策略这些「软件功能」的真实触点最后单独用一章说踩坑和排查。你不需要先读完全部文档再动手跟着每一章的代码和参数说明走就能在真实设备上逐步验证。2. CameraX 还是 Camera2先选对 API 层再谈功能全集2.1 两者的能力边界官方支持力度与你设备的真实上限Android 摄像头软件功能这件事水面上是你要做什么水面下是 API 层决定了你能控制到什么粒度。Camera2 API 是 Camera HAL3 的直接映射意味着你能拿到每一帧的曝光时间、ISO、光圈、传感器时间戳、是否处于 AE/AF 收敛状态甚至能通过 CaptureSession 设置自定义的 3A 策略。CameraX 则是在 Camera2 之上做了一层 use case 抽象把ImageCapture、Preview、VideoCapture封装成自带生命周期的组件。选择的第一原则不是「哪个新用哪个」而是「你需要控制到什么深度」。对比维度CameraXCamera2预览与拍照集成开箱即用绑定生命周期需要手动管理 Session 和 Surface对焦/曝光回调有分析器但粒度中等每个请求都能带 AE/AF 触发器和状态回调多摄与逻辑摄像头支持后置/前置切换逻辑摄像头跟随系统策略可以指定物理 ID但发现物理摄像头依赖 CameraManager录像与拍照并发VideoCapture ImageCapture 可同时绑定需要自行设计 ReprocessSession 或两个 Session 切换帧级定制受限适合常规场景可做手动曝光、RAW、HDR 多帧合成维护成本低系统 API 兼容性好高国产机型碎片化明显我一般会这样建议如果你做的是扫码工具、标准拍照 App、巡检拍照记录CameraX 是效率最高的如果你是做直播推流、老式设备兼容、需要手动感光度或帧级降噪的Camera2 才是正路。注意一个细节CameraX 底层仍然是 Camera2所以你通过Camera2CameraControl把 CameraX 的CameraControl强转成 Camera2 的 interop 接口在某些机型上可以拿到更细的控制能力。这是一个合规的逃生通道。2.2 CameraX 初始化工程骨架一个能跑通预览拍照的最小代码下面这段代码是一个通用的 CameraX 绑定骨架我把关键步骤都注释出来了。它解决的是「摄像头软件所有功能」中最底层的问题生命周期、预览、拍照三件事的同步。注意CameraX 的绑定是基于ProcessCameraProvider的单例每个生命周期只能绑定一次重复绑定会抛IllegalArgumentException。// 建议在 Activity 中实现 LifecycleOwner或使用 AppCompatActivity public class CameraXSetup { private Preview mPreview; private ImageCapture mImageCapture; private VideoCapture mVideoCapture; // 需要额外依赖 camera-video 扩展库 // 初始化获取 CameraProvider 是异步操作回调中再做绑定 public void startCamera(NonNull LifecycleOwner owner, NonNull ListenableFutureProcessCameraProvider cameraProviderFuture, NonNull PreviewView previewView) { cameraProviderFuture.addListener(() - { try { ProcessCameraProvider provider cameraProviderFuture.get(); // 构建 Preview use case目标分辨率优先选 720p降低配准与内存压力 Preview.Builder previewBuilder new Preview.Builder() .setTargetResolution(new Size(1280, 720)); // 注意不是屏幕分辨率 mPreview previewBuilder.build(); mPreview.setSurfaceProvider(previewView.getSurfaceProvider()); // 构建 ImageCapture use case用 JPEG 格式无需额外编解码 ImageCapture.Builder captureBuilder new ImageCapture.Builder() .setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY) // 追求快门响应速度 .setFlashMode(ImageCapture.FLASH_MODE_AUTO) // 自动闪光后文讲手动控制 .setJpegQuality(90); mImageCapture captureBuilder.build(); // 选择后置摄像头如果要兼容逻辑摄像头默认选默认后置即可 CameraSelector selector CameraSelector.DEFAULT_BACK_CAMERA; // 解绑旧 use case避免页面二次进来时冲突 provider.unbindAll(); provider.bindToLifecycle(owner, selector, mPreview, mImageCapture); } catch (Exception e) { // 常见异常摄像头被占用其他 App 常驻、设备不支持 CameraX // CameraX 在部分低端机上 bind 失败是常态需做降级处理 Log.e(CameraX, bind failed: e.getMessage()); } }, ContextCompat.getMainExecutor(context)); } // 拍照takePicture 的回调需要立刻处理或转码不要持有 Bitmap public void takePhoto(NonNull File outputFile) { ImageCapture.OutputFileOptions options new ImageCapture.OutputFileOptions.Builder(outputFile).build(); mImageCapture.takePicture(options, ContextCompat.getMainExecutor(context), new ImageCapture.OnImageSavedCallback() { Override public void onImageSaved(NonNull ImageCapture.OutputFileResults outputFileResults) { // 这里只是「文件已落盘」方向修正和 EXIF 在后面章节处理 } Override public void onError(NonNull ImageCaptureException exception) { // 常见错误码ERROR_CAPTURE_FAILED / ERROR_CAMERA_CLOSED / ERROR_INVALID_CAMERA } }); } }代码里的逻辑说明setTargetResolution不是最终输出分辨率而是让 CameraX 去选择「最接近这个目标」的可用流配置。你如果传Size(1920, 1080)在一些只有 4:3 或 16:9 非标准的摄像头上实际预览可能是 1600×720 或其他值。CAPTURE_MODE_MINIMIZE_LATENCY优先快门响应利于抓拍但在暗光下会造成噪点更明显需要画质优先就换成CAPTURE_MODE_MAXIMIZE_QUALITY。provider.unbindAll()是防止页面重建后重复绑定黑屏的关键很多初级项目翻车就在没做这一步。2.3 Camera2 手动链路从 CameraManager 到 CaptureSession如果你选择了 Camera2最小链路是四步打开摄像头、建立CaptureSession、创建CaptureRequest、循环取帧。我不建议从零手写CameraDevice.StateCallback和CameraCaptureSession.StateCallback因为人很容易在回调嵌套里搞混线程。更稳的做法是写一个基于HandlerThread的状态机把打开、配置、Session 建立三个状态显式地放在一个队列里。# 注意Android 摄像头逻辑都是 Java/Kotlin这里用一个「键值表」说明 Camera2 链路中必须设置的元数据字段 # 实际代码请参考 CameraManager.openCamera CameraDevice.createCaptureSession { camera_id: 0即后置主摄实际是 CameraManager.getCameraIdList()[0] 返回的字符串, surface_list: [预览 SurfaceTexture, ImageReader.Surface]两个 Surface 必须同属一个 session, capture_request_template: TEMPLATE_PREVIEW 用于预览/manual 用于手动控制/STILL_CAPTURE 用于拍照, ae_mode: CONTROL_AE_MODE_ON_AUTO_FLASH 或 CONTROL_AE_MODE_OFF 手动曝光, af_mode: CONTROL_AF_MODE_CONTINUOUS_PICTURE 用于拍照VIDEO 用于录像, jpeg_orientation: 依赖传感器方向值不能直接用屏幕方向 }参数说明TEMPLATE_PREVIEW在大多数设备上默认开启 3A也就是自动对焦、自动曝光、自动白平衡你只需要在预览请求上设置CONTROL_AF_MODE_CONTINUOUS_PICTURE就能得到一个连续对焦的预览画面。拍照时则需要重新提交一个TEMPLATE_STILL_CAPTURE请求并且等待CONTROL_AE_STATE_CONVERGED再触发捕获否则拍出来的照片曝光不稳定。这个「连续对焦预览 收敛后捕获」的组合是 Camera2 软件功能中最核心的交互设计新手容易一上来就想手动调 AF反而把自动策略弄坏了。3. 分辨率、方向与 EXIF三个最容易让「所有功能」翻车的硬参数3.1 预览变形不是摄像头的问题是分辨率配错很多人在「Android摄像头软件所有功能」这个需求里反复改预览布局发现图像被拉伸或裁剪然后怀疑摄像头坏了。其实绝大多数情况是PreviewView的布局尺寸和预览流尺寸比例不一致。CameraX 的PreviewView提供setScaleType来控制填充方式但真正优雅的做法是让画面充满且不裁切你需要知道系统选出来的预览流实际尺寸。用一个Preview.SurfaceProvider回调去拿分辨率// 通过 surface provider 拿到实际预览尺寸再反过来调整 UI previewView.surfaceProvider object : SurfaceProvider { override fun onSurfaceProvided(completionStage: SurfaceRequest, surface: Surface) { // surfaceRequest 内部持有分辨率信息此时拿到的是 CameraX 调整后的真实尺寸 surface.request.updateTransform() // 处理旋转 } }我通常的做法是在应用启动时用CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP列出所有输出尺寸再选择长宽比最接近 4:3 或 16:9 且面积最大的一组。注意若是用 Camera2 手动链路ImageReader的尺寸必须包含在 stream configuration map 里否则createCaptureSession直接抛IllegalArgumentException。另外还有一个低概率坑某些设备的摄像头传感器是横置的也就是传感器的行方向与屏幕自然方向相差 90 度此时SENSOR_ORIENTATION的值是 90 或 270需要按后置和前置分别做旋转补偿。3.2 EXIF 方向修正拍照文件旋转错误的真凶你以为你把JPEG_ORIENTATION设置为屏幕旋转角度就对了但你会发现前置摄像头拍出来的照片是镜像翻过来再转了 90 度的。原因是前置传感器方向 镜像标志需要合成。修正 EXIF 方向有三个要素传感器方向、摄像头前后、屏幕当前旋转。执行方式很简单但在代码里必须严格做一次合成计算// 计算 JPEG 方向input 为传感器朝向isFront 为前置标志displayRotation 为 UI 旋转角度 public static int computeJpegRotation(int sensorOrientation, boolean isFront, int displayRotation) { int result; if (isFront) { // 前置摄像头需要镜像处理所以旋转公式是 (sensorOrientation displayRotation) % 360 result (sensorOrientation displayRotation) % 360; } else { // 后置简单做差 (sensorOrientation - displayRotation 360) % 360 result (sensorOrientation - displayRotation 360) % 360; } return result; // 这个值直接填入 CaptureRequest.JPEG_ORIENTATION }参数说明sensorOrientation是由相机特性SENSOR_ORIENTATION给出的固定信息绝大多数后置是 90前置是 270displayRotation则是WindowManager.getDefaultDisplay().getRotation()换算成的 0/90/180/270不是你 SurfaceView 的旋转角度。如果拍出来的照片在相册里依然横竖不对建议先做一次实验固定手机竖屏、拍一张白底黑字图片然后导出 JPEG 查看其 EXIF 里的Orientation字段。实现过多次之后你会形成直觉大部分 Android 设备的后置摄像头 JPEG 方向等于sensorOrientation前置等于 0 或 180但华为和三星的若干机型会有差异只能在真机测试表里记录。3.3 录像与预览的方向保持录像出来的视频旋转像没法修正录像和拍照最大的不同是视频文件本身没有 EXIF 方向可以存最终播放器是靠MediaFormat的rotation字段来判断的。因此你在MediaRecorder或者VideoCapture输出时必须在编码参数里直接设置多少度旋转。CameraX 的VideoCapture会在内部自动读取传感器和 Display 的旋转并写入但当你使用 Camera2 MediaCodec 做自定义录制时需要自己处理。// MediaRecorder 手动路径的关键设置方向与预览保持一致 mediaRecorder.setOrientationHint(computeJpegRotation(sensorOrientation, isFront, displayRotation))这里只说一个常见误用很多人把setOrientationHint设成了固定 90在横屏录制时 OK但竖屏录制时视频在电脑上是躺着的。正确做法是启用一个OrientationEventListener在屏幕旋转时实时重起 MediaRecorder或者接受「录制中旋转方向不会中途改变」的限制至少保证录制开始时是正的。你要是做的是一个需要持续录制几分钟的工具软件屏幕上最好放一个「旋转锁定」提示避免录到一半画面翻转成 90 度处理起来比拍照 EXIF 麻烦得多。4. 对焦、曝光与灯光控制把硬件参数的「软件开关」做全4.1 自动对焦和手动对焦的协同三种触发模式「摄像头软件所有功能」里的对焦不能只写一个CONTROL_AF_MODE_AUTO因为用户会点屏幕触发对焦会回到连续对焦模式会手动拉焦距数码变焦还有可能扫码时想锁定对焦。对焦控制本质上是一个三态状态机连续对焦、单次对焦、锁定对焦。用户操作AF 模式实现方式预览启动CONTINUOUS_PICTURECaptureRequest 默认设置点击屏幕对焦AUTO AF_TRIGGER_START触发CONTROL_AF_TRIGGER_START等待AE/AF_STATE收敛长按锁定对焦AUTO AF_TRIGGER_CANCEL 后置为 CONTINUOUS 并取消触发设置CONTROL_AF_REGIONS固定到触摸区域扫码/文档场景MACRO 模式部分设备支持先查询CONTROL_AF_AVAILABLE_MODES在实际的 CameraX 路径里你用CameraControl.startFocusAndMetering传入一个FocusMeteringAction它可以同时指定对焦区域和测光区域。这个动作的点是FocusMeteringAction.Builder支持setAutoCancelDuration我建议设置成 800ms意思是点在屏幕上对焦成功后的 0.8 秒内不启动连续对焦避免一松手画面立刻重新糊掉。不少项目这个参数设置太短用户对焦还没看清画面又自动对到别处去了体验非常糟糕。4.2 曝光控制AE 状态机比手动设置感光度更重要曝光控制里 80% 的坑不在数值设置上而在「什么时候允许设置、什么时候读回状态」。Camera2 的 AE 是异步收敛状态机典型的触发序列是INACTIVE - SEARCHING - CONVERGED或FLASH_REQUIRED。当你手动设置曝光补偿时如果不在CONVERGED状态就下发有些设备会忽略这次设置或者产生闪烁。// 通过 CaptureResult 检查 AE/AF 收敛状态的推荐路径 if (result.get(CaptureResult.CONTROL_AE_STATE) CaptureResult.CONTROL_AE_STATE_CONVERGED) { // 此时用户滑动曝光补偿条才实际生效UI 可以放开亮度调节滑块 }曝光补偿的范围用CameraCharacteristics.CONTROL_AE_COMPENSATION_RANGE查询通常为 -12 到 12步长 1别硬编码 -10 到 10部分设备是 -8 到 8。还有一个规律曝光补偿步长是CONTROL_AE_COMPENSATION_STEP默认是 0.5 EV 或 1 EV你滑块的每个格都应该是这个值的整数倍否则相邻两档亮度变化不均。如果你做的是专业相机模式还需要注意CONTROL_AE_LOCK与手动感光度的互斥——开了 AE_LOCK 再设置SENSOR_EXPOSURE_TIME会被无视因为 AE 策略还在控制它。4.3 闪光灯不是「打开这么简单」AUTO 闪光的失败循环和 LED 手电筒闪光灯在 CameraX 里有三种状态FLASH_MODE_AUTO、FLASH_MODE_ON、FLASH_MODE_OFF。很多人忽略了一个致命逻辑AUTO 闪光模式下设备是靠 AE 测光结果「事后」决定闪不闪的也就是说你在暗处按快门会感觉快门延迟变长然后拍出来的照片往往欠曝或过曝。这是因为CONTROL_AE_MODE_ON_AUTO_FLASH驱动 AE 在闪光前先做一次预闪测光。如果你想做「所见即所得」的抓拍体验在暗光下建议手动把闪光模式改成ON同时接受高光溢出的风险。LED 手电筒则要绕开相机会话直接用CameraManager.setTorchMode(cameraId, true)。注意这与相机会话是抢独占关系——如果相机已经在预览手动打开手电筒会要求你的 App 释放 Camera 设备或至少释放预览链路否则会出现无法打开、提示 Camera in use 的情况。有人想边录像边开手电筒这是可能的做法是在录像会话里设置FLASH_MODE_TORCH而不是调手电筒系统接口。// 录像同时强制打开 LEDFLASH_MODE_TORCH注意要在录像启动前设置 val request cameraDevice.createCaptureRequest(TEMPLATE_RECORD) request.set(CaptureRequest.FLASH_MODE, CameraMetadata.FLASH_MODE_TORCH)参数说明TEMPLATE_RECORD本身会优化为持续录像的配置比手动TEMPLATE_PREVIEW设置FLASH_MODE_TORCH更稳因为系统知道后面的 session 会持续使用这个请求。而且用完成后要立刻发一个FLASH_MODE_OFF的新请求否则到了预览 state 里灯光还亮着就会有「预览正常、开关无效」的诡异现象。4.4 数码变焦、缩放和点多摄切换软件功能的最后一公里数码变焦的实质是裁剪传感器输出区域。CameraX 的CameraControl.setZoomRatio或 Camera2 的CONTROL_ZOOM_REGION二者都是设置 crop region。在「所有功能」的视角里这个特性是最容易做但不一定被做对的很多人直接在 UI 上让用户拖动zoomRatio但忘了变焦后的画面清晰度下限、以及变焦后对焦区域需要重新映射。当前 Android 的多摄是「逻辑摄像头」概念也就是说相机列表里的 0 号 ID 可能不是物理主摄而是一个融合了广角 主摄 长焦逻辑设备。你单纯去遍历CameraManager.getCameraIdList()找LENS_FACING_BACK会漏掉超广角和长焦。正确做法是看CameraCharacteristics.LOGICAL_MULTI_CAMERA和LENS_INFO_AVAILABLE_FOCAL_LENGTHS如果某个逻辑摄像头包含多种焦距但 physical ids 输出不同那么你就是通过setPhysicalCameraIdCameraX 的Camera2CameraControlinterop 支持或者 Camera2 的 capture request 里指定物理 ID 来切换镜头。5. 摄像头软件功能避坑预览黑屏、拍照绿图、录像无声、权限反复弹5.1 预览黑屏Surface 时序错位和 CameraX unbind 漂移现象预览区域一片黑日志没有致命错误CameraDevice.StateCallback已经回调到opened但画面始终不显示。原因绝大多数情况是 Surface 在 CapSession 创建前没有被有效配置或者 Surface 已经失效Activity 重建但系统仍持有旧引用。Camerax 在bindToLifecycle时如果传入的PreviewView还没有 attach 到 windowSurfaceProvider拿到的 SurfaceRequest 会一直处于 pending 状态你的预览自然黑屏。解决在onResume之后再执行startCamera并确保PreviewView宽高已被布局框架测量。调试时用previewView.isLaidOut()做判空。一个习惯很重要执行unbindAll()后要间隔一个主线程循环再去绑定CameraX内部释放旧 session 是异步的连续绑定太快会黑屏一时再恢复。5.2 拍照绿图/半边花屏ImageReader 格式或 JPEG 尺寸异常现象拍出来的 JPEG 文件打开是绿色的或者上半部分正常下半部分花屏用 ImageReader 拿字节数组后转 Bitmap 颜色错乱。原因ImageReader的ImageFormat设置成了RAW_SENSOR却按YUV_420_888解析或者你拿到的Imageplane 数组没有按 rowStride 去复制。另一个常见情况是 JPEG 捕获请求里同时指定了JPEG_QUALITY和JPEG_ORIENTATION但实际流配置里没声明ImageFormat.JPEG用的尺寸系统只能输出一个代理尺寸字节长度对不上。解决统一使用ImageFormat.JPEG作为 output 格式ImageReader的宽高明确和StreamConfigurationMap对齐。如果是 YUV 数据不要用默认的data[0].limit()去拷贝用rowStride * height计算实际数据长度遇到MediaCodec的输入 buffer 花屏则还要确认COLOR_FormatYUV420Flexible和实际 plane 的对齐方式。5.3 录像没有声音AudioSource 或权限时序被摄像头的 Surface 创建抢先现象录像文件播放画面正常但音轨是空或录到一半声音突然消失。原因MediaRecorder 的setAudioSource和setVideoSource顺序写反是常见翻车事件更隐蔽的是录音权限在录制启动时才动态弹出用户点「允许」的那几秒内 MediaRecorder 已经 start之后虽然授权成功但录音源已经失效。还有一个常见问题很多国产设备在做「相机与麦克风同时使用」时需要在 manifest 里声明android.permission.CAMERA和android.permission.RECORD_AUDIO但 Android 12 以上「麦克风访问」有单独的「仅在使用时允许」开关如果用户在系统设置里改成了「每次询问」你的程序进入前台时不会收到权限回调但麦克风实际是关闭的。解决动态请求权限时把 CAMERA 与 RECORD_AUDIO 放到同一个请求列表等所有授予后再MediaRecorder实例化。录制中途如果检测到MediaRecorder的onInfo(whatMEDIA_RECORDER_INFO_MAX_DURATION_REACHED)或错误回调应当立即重置 recorder 并告知用户「录制中断」而不是静默吞掉。5.4 相机权限已授予但扫码界面依然黑屏隐私指示器与「摄像头占用」现象App 有相机权限、onResume 正常执行、CameraManager 能列出摄像头但 bindToLifecycle 失败日志提示LEGACY_CAMERA_DEVICE或CAMERA_DISCONNECTED。原因某些 Android 13 设备在用户下拉快捷开关里开启了「摄像头访问」禁用或者另一个 App 持有相机而目标设备不支持并发会话。还有相当一部分情况是安全软件或系统级扫码组件占据了摄像头。解决在绑定失败时用CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL进行检查同时监听ACTION_CAMERA_DISCONNECTED广播部分设备可用提示用户关闭其他占用相机的 App。如果硬件级别是LEGACY请降级到 Camera1 API 或放宽分辨率要求——这是你的 App 与系统性兼容的兜底方案不是玄学。5.5 前置摄像头画面左右翻转镜像是拍照预览的「正确但不合适」现象用前置摄像头预览时画面和你照镜子一致但保存的 JPEG 却是左右反的或反过来预览是镜像但文件是正的。原因Android 系统没有强制前置预览镜像SCALER_CROP_REGION和传感器读出的方向不会因为前置而自动翻转。Camera2 下你看到的镜像效果是很多 App 额外做的预览层 Matrix 镜像而不是系统输出。文件方向则只看 JPEG_ORIENTATION 值。解决如果产品要求「所见即所得」请只在预览层做镜像翻转PreviewView.setScaleX(-1)或TextureView的 Matrix 设置文件输出保持方向提示正确。千万别尝试在 EXIF 里写镜像标志因为浏览器和图库的 EXIF 方向支持程度并不一致。这个需求最常出现在「自拍美颜 实时预览」方案里前后测样张确认一次即可。6. 最后一公里帧率、延迟与性能指标的验证技巧功能做到「有画面、能拍照」不难难的是跟同类型 App 比你的点击到快门延迟多 200ms、预览帧率掉到 15fps、录像时手机烫得厉害。验证不要靠肉眼用 adb 与性能分析工具把指标打出来。我会用三个抓手帧率统计、打开相机的冷启动时长、以及拍照到结果回调的链路耗时。# 帧率统计基于 gfxinfo 的输出可以看到 janky frames 与总帧数 adb shell dumpsys gfxinfo your.package.name | grep Total frames -A 5 # 拍照链路耗时建议在代码里打点onCaptureStarted - onCaptureCompleted 就是真实快门延迟 adb logcat -s CameraPerf | grep SHUTTER_LATENCY另一个被开发者忽略的性能指标是预览流的格式。CameraX 默认预览是YUV_420_888如果你把 UI 放在了低端机上setTargetResolution(1280, 720)与PreviewView的PerformanceMode组合可以压出更高的帧率。把Preview.Builder.setPerformanceMode(Preview.PERFORMANCE_MODE_FASTEST)设置上并且把预览的分辨率控制在 1080p 以下这是低端机流畅度的关键。我保持着一个小习惯每次改完摄像头相关逻辑都会用「冷启动 自动对焦 连拍三张 退出页面」做一轮验证把dumpsys media.camera的底层日志保存一份。这些日志里包含摄像头打开到关闭的完整记录也是排查「某机型崩溃」时的第一手证据。希望这篇关于 Android 摄像头软件功能全集的整理能帮你少走几段弯路也希望你在真机上验证每一条参数——摄像头这层真机永远比文档诚实。本文还有配套的精品资源点击获取