1. 从“黑盒”到“白盒”为什么我们需要理解Camera接口如果你是一名Android应用开发者或者正在涉足音视频、图像处理领域那么“Camera接口”这个词对你来说一定不陌生。你可能已经熟练地调用了Camera.open()设置了一堆参数然后拿到了预览画面。但你是否曾有过这样的困惑为什么同样的代码在A手机上预览流畅在B手机上却卡顿甚至崩溃为什么设置一个简单的对焦模式在不同厂商的设备上效果天差地别为什么文档里说支持的某个分辨率实际调用时却报错这些问题根源往往不在于你的Java或Kotlin代码逻辑而在于你与手机摄像头硬件之间那层至关重要的“翻译官”——Camera接口。在Android生态的早期这个“翻译官”就是android.hardware.Camera。它就像一个封装严密的黑盒我们告诉它“拍照”它给我们一张图片。至于它内部如何与高通、联发科、三星的芯片以及索尼、三星、豪威的传感器沟通我们一无所知。这种黑盒模式在初期简化了开发但随着摄像头硬件功能爆炸式增长多摄、深度信息、RAW格式、高帧率录像其僵化、封闭的架构成了创新的瓶颈。因此理解Camera接口本质上是在理解移动设备上图像采集的“工作流”和“权力边界”。这不再是简单的API调用而是涉及硬件抽象层HAL、数据流管道、性能调优和碎片化兼容的系统性工程。无论是为了开发一款体验出色的相机应用还是为了实现AR测量、人脸识别、文档扫描等高级功能深入Camera接口的细节都是绕不开的一课。今天我们就抛开那些笼统的概念直接深入到接口设计的逻辑、版本迭代的缘由以及实际编码中那些“坑”的成因里把Camera接口从“黑盒”变成我们手中的“白盒”。2. Camera1奠基者的功勋与历史包袱当我们谈论Camera接口必须从起点开始这个起点就是Camera API 1通常我们直接称之为android.hardware.Camera。它是Android系统为摄像头功能提供的第一个标准化接口其设计哲学深深烙印着那个时代的印记以拍照为中心流程驱动。2.1 核心工作模型状态机与回调机制Camera1 API的核心是一个清晰但略显笨重的状态机。你必须严格遵循打开 - 配置 - 预览 - 捕获 - 释放的生命周期。这个模型非常直观但缺乏灵活性。打开与配置调用Camera.open(cameraId)是第一步。这里第一个坑就出现了早期的设备通常只有后置id0和前置id1两个摄像头但多摄时代来临后这个简单的映射关系完全失效。你需要通过Camera.getNumberOfCameras()获取数量并遍历Camera.CameraInfo来识别摄像头的朝向和用途。配置主要通过Camera.Parameters对象完成。这是一个包含了所有可调参数的“大袋子”。你需要这样操作Camera camera Camera.open(0); Camera.Parameters params camera.getParameters(); // 设置预览尺寸 params.setPreviewSize(1920, 1080); // 设置图片尺寸 params.setPictureSize(4032, 3024); // 设置对焦模式 params.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE); // 应用参数 camera.setParameters(params);这个过程看似简单却隐藏着兼容性噩梦。setPreviewSize和setPictureSize必须使用getSupportedPreviewSizes()和getSupportedPictureSizes()返回的列表中的值但不同厂商的列表差异巨大。更棘手的是某些参数组合是不兼容的。例如你设置了某个高分辨率预览尺寸可能就会导致自动对焦模式不可用而API并不会明确告诉你只会在后续预览时出现异常或 silently fail静默失败。2.2 数据流处理SurfaceView与字节数组Camera1提供两种数据获取方式预览到Surface通过camera.setPreviewDisplay(surfaceHolder)或camera.setPreviewTexture(surfaceTexture)将预览画面直接渲染到SurfaceView或TextureView上。这是最常用、性能最好的方式因为数据直接传递给了显示系统无需经过应用层内存拷贝。预览回调通过camera.setPreviewCallback(...)注册回调每一帧预览数据都会以byte[]的形式回调到应用层。这给了你处理每一帧图像的能力如实时滤镜、人脸检测但代价巨大。将YUV或NV21格式的数据从Native层拷贝到Java层的字节数组是极其消耗CPU和内存的操作会迅速导致手机发烫和预览卡顿。注意很多初学者为了做实时处理滥用PreviewCallback结果导致应用性能崩溃。正确的做法是如果必须处理使用setPreviewCallbackWithBuffer并配合缓冲区复用或者直接使用setPreviewTexture并基于SurfaceTexture的OnFrameAvailableListener在Native层或RenderThread中进行处理避免跨层拷贝。2.3 Camera1的“历史包袱”与经典陷阱Camera1的设计在今天看来有诸多缺陷这些缺陷也成了我们开发中的经典陷阱参数设置的非原子性getParameters和setParameters不是原子操作。你在get之后可能其他线程或系统已经修改了摄像头状态导致你set的参数基于一个过时的状态从而引发不可预知的问题。在高动态场景如快速切换前后置摄像头下尤为明显。全局单例与生命周期管理Camera对象本身是一个重量级资源且同一时间只能有一个实例被打开。如果你的应用没有妥善管理例如在Activity的onPause中未释放Camera而另一个Activity又尝试打开就会导致崩溃。你必须严格遵守“谁打开谁释放”的原则并在onPause中确保释放。对焦与测光分离自动对焦(autoFocus)和自动测光(autoExposureLock)是分开的调用且流程复杂。要实现触摸对焦并显示对焦框你需要监听触摸事件将坐标从视图坐标系转换为摄像头传感器坐标系然后调用camera.autoFocus(callback)。这个过程涉及坐标变换容易出错。缺乏精细的元数据控制你无法单独控制曝光时间、ISO感光度、白平衡色温等底层参数只能通过有限的场景模式如夜景、运动来间接影响无法满足专业摄影或计算摄影的需求。尽管有这些缺陷Camera1因其广泛的兼容性直到Android 5.0都是主流至今仍是一些对兼容性要求极高、功能简单的场景下的备选方案。理解它是理解后续所有Camera API演进的基础。3. Camera2面向未来的重构与复杂性激增为了彻底解决Camera1的顽疾Android 5.0 (API 21) 引入了全新的Camera2 API (android.hardware.camera2)。它的设计哲学发生了根本性转变从以拍照为中心变为以数据流为中心从流程驱动变为异步事件驱动。它把摄像头抽象为一个可以向其发送捕获请求、并接收其捕获结果的设备更贴近现代摄像头的实际工作方式。3.1 核心架构管道模型与异步请求Camera2的核心是“管道”模型。你可以把它想象成一个高级餐厅的厨房CameraManager餐厅经理。负责列出所有可用的摄像头getCameraIdList并提供其能力菜单getCameraCharacteristics。CameraCharacteristics每个摄像头的“能力菜单”。里面详细列出了该摄像头支持的所有功能、分辨率范围、硬件等级等信息。在操作前你必须仔细研读这份菜单。CameraDevice具体的厨师摄像头硬件。通过openCamera获得。CaptureRequest顾客点的“菜谱”。你定义想要什么如输出到哪个Surface对焦模式曝光补偿。CaptureRequest.Builder菜谱的草稿纸。通过createCaptureRequest获得你可以往里面添加各种“食材”输出目标和“调味要求”参数。CameraCaptureSession一个已经建立好的、高效的“传菜通道”。它连接了CameraDevice和一组输出Surface比如预览的SurfaceView和拍照的ImageReader。创建Session是一个耗时操作。Surface装菜的“盘子”。可以是用于预览的SurfaceView/TextureView的Surface也可以是用于拍照的ImageReader的Surface甚至是用于录像的MediaRecorder的Surface。工作流程变为查询菜单 - 聘请厨师 - 准备盘子 - 建立传菜通道 - 不断发送菜谱请求 - 在对应的盘子里收到做好的菜图像数据。3.2 异步操作与状态回调Camera2的所有重要操作都是异步的通过回调通知结果。这避免了UI线程阻塞但大大增加了代码的复杂度。// 1. 获取CameraManager CameraManager manager (CameraManager) context.getSystemService(Context.CAMERA_SERVICE); // 2. 获取摄像头特性能力菜单 CameraCharacteristics characteristics manager.getCameraCharacteristics(cameraId); // 3. 异步打开摄像头 manager.openCamera(cameraId, new CameraDevice.StateCallback() { Override public void onOpened(NonNull CameraDevice camera) { // 摄像头已打开获得CameraDevice对象 mCameraDevice camera; // 4. 创建预览请求的Builder mPreviewRequestBuilder mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); // 5. 创建用于预览的Surface例如来自TextureView SurfaceTexture texture mTextureView.getSurfaceTexture(); texture.setDefaultBufferSize(previewSize.getWidth(), previewSize.getHeight()); Surface previewSurface new Surface(texture); mPreviewRequestBuilder.addTarget(previewSurface); // 将Surface加入请求目标 // 6. 创建CaptureSession传菜通道 ListSurface outputSurfaces Arrays.asList(previewSurface, mImageReader.getSurface()); mCameraDevice.createCaptureSession(outputSurfaces, new CameraCaptureSession.StateCallback() { Override public void onConfigured(NonNull CameraCaptureSession session) { mCaptureSession session; // 7. 设置重复请求开始预览 mPreviewRequest mPreviewRequestBuilder.build(); mCaptureSession.setRepeatingRequest(mPreviewRequest, null, null); } Override public void onConfigureFailed(NonNull CameraCaptureSession session) { // 配置失败处理 } }, null); } Override public void onDisconnected(NonNull CameraDevice camera) { /*...*/ } Override public void onError(NonNull CameraDevice camera, int error) { /*...*/ } }, mBackgroundHandler); // 必须指定一个后台Handler线程这段代码仅仅是建立预览就已经嵌套了多层回调。任何一个环节出错都需要在对应的错误回调中妥善处理否则会导致资源泄露或应用无响应。3.3 Camera2的优势与“高门槛”Camera2带来了巨大的灵活性提升并发数据流可以同时向预览Surface、拍照ImageReader和录像MediaRecorder发送数据轻松实现“边录边拍”。精细参数控制可以直接设置传感器曝光时间、ISO、镜头焦距等底层参数为专业模式铺平道路。更高效的缓冲区管理通过ImageReader获取图像缓冲区可以在Native层和Java层之间更高效地共享减少了内存拷贝。更丰富的元数据每一帧捕获结果都附带大量的元数据对焦状态、曝光状态、时间戳等便于进行高级图像处理。然而其复杂性也成了“高门槛”样板代码极多完成一个基础的预览拍照功能代码量是Camera1的5-10倍。异步错误处理复杂各种状态回调StateCallbackCaptureCallback分散在不同的地方错误处理链路长调试困难。设备兼容性仍需检查虽然接口统一但不同设备的CameraCharacteristics能力菜单差异巨大。你必须通过check方法动态判断是否支持某个特性如FLASH_MODE_OFFCONTROL_AF_MODE_CONTINUOUS_PICTURE不能想当然。性能调优点分散缓冲区大小、ImageReader的数量、请求模板TEMPLATE_PREVIEWTEMPLATE_RECORDTEMPLATE_STILL_CAPTURE的选择都影响着性能和功耗需要仔细权衡。4. CameraX谷歌的“和解方案”与最佳实践面对Camera2的复杂性以及Camera1的过时谷歌推出了Jetpack组件库中的CameraX。它的目标不是替代Camera2而是在其之上构建一个生命周期感知、用例驱动、向后兼容的抽象层。你可以把它理解为谷歌官方提供的、针对常见摄像头场景的“最佳实践套件”。4.1 核心概念用例、生命周期与选择器CameraX引入了几个关键概念极大地简化了开发用例将复杂的摄像头操作封装成几个明确的场景。PreviewView用于预览。它内部封装了TextureView或SurfaceView并自动处理尺寸变换和旋转。ImageCapture用于拍摄高画质照片。ImageAnalysis用于逐帧图像分析如二维码识别、人脸检测。它提供了ImageProxy对象比Camera1的PreviewCallback更高效。VideoCapture用于录制视频。生命周期绑定CameraX与LifecycleOwner如Activity/Fragment绑定。当生命周期处于STARTED状态时摄像头自动打开并运行当进入STOPPED状态时自动释放。你几乎不再需要手动管理open和release。CameraSelector一个优雅的选择器用于选择前后置摄像头而无需处理复杂的ID和特性查询。4.2 快速上手指南与代码对比使用CameraX实现一个带预览和拍照的功能代码简洁得令人惊讶// 1. 创建用例 val preview Preview.Builder().build() val imageCapture ImageCapture.Builder() .setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY) // 设置拍照模式最低延迟或最高质量 .build() // 2. 创建选择器选择后置摄像头 val cameraSelector CameraSelector.DEFAULT_BACK_CAMERA // 3. 将预览用例绑定到PreviewView布局中定义的视图 preview.setSurfaceProvider(previewView.surfaceProvider) // 4. 绑定生命周期 val cameraProviderFuture: ListenableFutureProcessCameraProvider ProcessCameraProvider.getInstance(context) cameraProviderFuture.addListener({ val cameraProvider: ProcessCameraProvider cameraProviderFuture.get() try { // 解绑所有用例 cameraProvider.unbindAll() // 绑定用例到生命周期和摄像头 val camera cameraProvider.bindToLifecycle( this as LifecycleOwner, // 生命周期所有者 cameraSelector, // 摄像头选择器 preview, // 预览用例 imageCapture // 拍照用例 ) // 现在预览已经自动开始拍照功能也已就绪 } catch (exc: Exception) { // 错误处理 } }, ContextCompat.getMainExecutor(context)) // 在主线程执行回调 // 5. 拍照 fun takePhoto() { val outputFileOptions ImageCapture.OutputFileOptions.Builder(File(...)).build() imageCapture.takePicture( outputFileOptions, ContextCompat.getMainExecutor(context), object : ImageCapture.OnImageSavedCallback { override fun onImageSaved(outputFileResults: ImageCapture.OutputFileResults) { // 拍照成功 } override fun onError(exception: ImageCaptureException) { // 拍照失败 } } ) }对比Camera2的数十行嵌套回调CameraX的代码清晰、线性且自动处理了绝大部分兼容性和生命周期问题。4.3 CameraX的适用场景与局限性CameraX是绝大多数应用开发者的首选但它并非万能优势极简API快速实现主流功能。自动兼容在底层自动选择Camera2或Camera1实现提供一致的API。设备一致性谷歌通过“一致性测试”确保不同设备上行为一致减少了碎片化问题。生命周期安全杜绝了资源泄露。局限性功能封装它封装了常见场景。如果你需要Camera2提供的极其精细的控制如手动设置精确到纳秒的曝光时间或者需要实现非常规的数据流组合CameraX可能无法直接满足需要回退到Camera2 API。性能开销作为抽象层它带来轻微的运行时开销。对于性能极限敏感的应用如超高帧率慢动作录制可能需要直接使用Camera2。新特性支持滞后最新的硬件特性如某些传感器独有的功能可能需要等待CameraX更新适配。实操心得对于90%的应用场景社交拍照、扫描、简单录像直接采用CameraX是最佳选择。从项目开始就引入CameraX能节省大量开发和调试时间。只有在明确需要CameraX不支持的专业级控制时才考虑直接使用Camera2。5. 实战中的共性难题与排查思路无论你选择哪个版本的API在实际开发中都会遇到一些共性的棘手问题。下面我将这些问题的排查思路梳理成一个链路你可以像查字典一样使用。5.1 问题一预览画面拉伸、变形或方向错误这是最常见的问题之一根本原因在于摄像头传感器输出、预览Surface尺寸、视图显示区域三者之间的宽高比和旋转角度不匹配。排查链路确定传感器输出尺寸通过CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE(Camera2) 或Camera.Parameters.getSupportedPreviewSizes()(Camera1) 获取。这是一个固定的物理像素矩阵。确定预览Surface尺寸这是你为预览分配缓冲区的尺寸。在Camera2中你需要从StreamConfigurationMap.getOutputSizes(SurfaceTexture::class.java)中选择在Camera1中从getSupportedPreviewSizes()中选择。关键点这个尺寸的宽高比应尽可能与上一步中传感器输出尺寸的宽高比一致以避免由硬件缩放引起的画质损失和额外功耗。确定视图显示尺寸你的PreviewView、TextureView或SurfaceView在屏幕上的实际宽高。计算与适配比例适配比较预览Surface尺寸和视图显示尺寸的宽高比。如果不同你需要决定是“填满”可能裁剪还是“适应”可能留黑边。CameraX的PreviewView默认提供了多种ScaleType如FIT_CENTERFILL_CENTER来自动处理。旋转适配通过CameraCharacteristics.SENSOR_ORIENTATION获取传感器自然方向通常是横屏方向。再结合设备当前物理方向和应用界面固定方向计算出需要旋转的度数并通过setDisplayOrientation(Camera1) 或设置CaptureRequest的JPEG_ORIENTATION/SCALER_CROP_REGION(Camera2) 来校正。CameraX的PreviewView和ImageCapture会自动处理旋转。注意很多开发者忽略了传感器方向直接设置显示旋转导致前置摄像头预览镜像错误。正确处理旋转和镜像逻辑是保证预览和成片方向正确的关键。5.2 问题二对焦失败、拍照延迟高对焦问题通常源于场景低对比度、低光照、硬件限制或参数配置不当。排查链路检查对焦模式支持首先确认选中的摄像头支持你要求的对焦模式如CONTINUOUS_PICTURE。通过CameraCharacteristics.CONTROL_AF_AVAILABLE_MODES(Camera2) 或Camera.Parameters.getSupportedFocusModes()(Camera1) 查询。分析场景在纯色墙面、黑暗环境或快速移动场景下任何对焦系统都可能失败。可以尝试切换到FOCUS_MODE_INFINITY或FOCUS_MODE_FIXED如果支持或者提示用户改善拍摄环境。Camera1特有陷阱确保在调用autoFocus之前预览已经启动。在setParameters后立即调用autoFocus可能会失败。Camera2的异步性在Camera2中对焦是一个状态机CONTROL_AF_STATE_*。发送一个对焦请求CONTROL_AF_TRIGGER_START后需要监听CaptureResult.CONTROL_AF_STATE的变化直到它进入FOCUSED或NOT_FOCUSED状态才能判定对焦完成。不能假设发送请求后立即就对焦好了。拍照延迟高延迟往往是因为使用了TEMPLATE_STILL_CAPTURE模式但未正确控制曝光。在低光下自动曝光可能需要较长时间。可以尝试在拍照前先锁定曝光CONTROL_AE_PRECAPTURE_TRIGGER或者使用TEMPLATE_ZERO_SHUTTER_LAG模式如果设备支持。5.3 问题三内存溢出与性能瓶颈摄像头是数据吞吐大户处理不当极易引起OOM和卡顿。排查链路审视数据路径避免Java层回调绝对不要在Camera1中使用PreviewCallback进行高频、复杂的图像处理。如果必须使用setPreviewCallbackWithBuffer并复用缓冲区。使用ImageReader在Camera2中使用ImageReader获取图像。通过ImageReader.newInstance()设置合适的maxImages参数通常2-3张即可并在OnImageAvailableListener中及时调用image.close()释放缓冲区。持有Image对象不释放是常见的内存泄漏源。选择合适分辨率不要盲目选择最高分辨率。预览使用1080P或720P通常足够分析用例根据算法需要选择最低可接受分辨率。管理生命周期确保在onPause或生命周期回调中及时关闭摄像头、停止预览、释放所有ImageReader和Surface。监控内存使用Android Profiler监控Java堆和Native堆内存。如果看到Native内存持续增长很可能是在Native层如MediaCodec Camera HAL有泄漏虽然你的Java代码可能已经释放了引用。线程管理Camera2的所有回调默认在Handler指定的线程执行。如果在这个回调中进行耗时操作如图像处理会阻塞后续的摄像头请求导致预览卡顿。务必确保回调方法快速返回将耗时任务抛到其他工作线程。6. 版本选择与架构设计建议面对三个主要的Camera API该如何选择这取决于你的应用目标、团队资源和设备覆盖要求。决策矩阵参考考量维度Camera1Camera2CameraXAPI 复杂度低极高低功能控制力弱受限极强可精细控制中等覆盖主流场景设备兼容性仅支持旧设备API 21支持 API 21但不同厂商实现差异大支持 API 21通过适配层提供一致行为开发速度快但已过时慢非常快维护成本低但功能有限高低推荐场景维护极其老旧的应用或仅需最基本功能且必须覆盖Android 4.x开发专业相机应用、需要极致性能控制、使用特殊硬件功能绝大多数现代应用、快速原型、希望降低碎片化影响架构设计建议 对于新项目我的强烈建议是以CameraX为默认和首选方案。它解决了95%的需求并且随着谷歌的维护其功能和稳定性会持续增强。在你的应用架构中可以将摄像头操作封装在一个独立的模块或ViewModel中。创建CameraController封装CameraX的初始化、用例绑定、生命周期控制、权限检查等逻辑。定义状态接口对外暴露简单的接口如startPreview()takePhoto(callback)switchCamera()setFlashMode(mode)。将复杂的CameraX API调用隐藏在内部。错误处理统一化在Controller内部将CameraX的各种异常CameraUnavailableExceptionImageCaptureException等转换为应用层能理解的统一错误码或消息通过LiveData或回调传递出去。为高级功能留出扩展口在Controller内部可以保留获取底层Camera2CameraControl和Camera2CameraInfoCameraX提供的扩展接口的能力。当未来需要实现CameraX未封装的高级功能时可以通过这些扩展接口直接操作部分Camera2能力而无需重构整个摄像头模块。这样的设计既享受了CameraX的开发效率与兼容性红利又为未来的功能扩展预留了可能性。摄像头开发不再是令人望而生畏的“深水区”而是一个可以被清晰管理和迭代的功能模块。
Android Camera接口演进:从Camera1到CameraX的实战解析
1. 从“黑盒”到“白盒”为什么我们需要理解Camera接口如果你是一名Android应用开发者或者正在涉足音视频、图像处理领域那么“Camera接口”这个词对你来说一定不陌生。你可能已经熟练地调用了Camera.open()设置了一堆参数然后拿到了预览画面。但你是否曾有过这样的困惑为什么同样的代码在A手机上预览流畅在B手机上却卡顿甚至崩溃为什么设置一个简单的对焦模式在不同厂商的设备上效果天差地别为什么文档里说支持的某个分辨率实际调用时却报错这些问题根源往往不在于你的Java或Kotlin代码逻辑而在于你与手机摄像头硬件之间那层至关重要的“翻译官”——Camera接口。在Android生态的早期这个“翻译官”就是android.hardware.Camera。它就像一个封装严密的黑盒我们告诉它“拍照”它给我们一张图片。至于它内部如何与高通、联发科、三星的芯片以及索尼、三星、豪威的传感器沟通我们一无所知。这种黑盒模式在初期简化了开发但随着摄像头硬件功能爆炸式增长多摄、深度信息、RAW格式、高帧率录像其僵化、封闭的架构成了创新的瓶颈。因此理解Camera接口本质上是在理解移动设备上图像采集的“工作流”和“权力边界”。这不再是简单的API调用而是涉及硬件抽象层HAL、数据流管道、性能调优和碎片化兼容的系统性工程。无论是为了开发一款体验出色的相机应用还是为了实现AR测量、人脸识别、文档扫描等高级功能深入Camera接口的细节都是绕不开的一课。今天我们就抛开那些笼统的概念直接深入到接口设计的逻辑、版本迭代的缘由以及实际编码中那些“坑”的成因里把Camera接口从“黑盒”变成我们手中的“白盒”。2. Camera1奠基者的功勋与历史包袱当我们谈论Camera接口必须从起点开始这个起点就是Camera API 1通常我们直接称之为android.hardware.Camera。它是Android系统为摄像头功能提供的第一个标准化接口其设计哲学深深烙印着那个时代的印记以拍照为中心流程驱动。2.1 核心工作模型状态机与回调机制Camera1 API的核心是一个清晰但略显笨重的状态机。你必须严格遵循打开 - 配置 - 预览 - 捕获 - 释放的生命周期。这个模型非常直观但缺乏灵活性。打开与配置调用Camera.open(cameraId)是第一步。这里第一个坑就出现了早期的设备通常只有后置id0和前置id1两个摄像头但多摄时代来临后这个简单的映射关系完全失效。你需要通过Camera.getNumberOfCameras()获取数量并遍历Camera.CameraInfo来识别摄像头的朝向和用途。配置主要通过Camera.Parameters对象完成。这是一个包含了所有可调参数的“大袋子”。你需要这样操作Camera camera Camera.open(0); Camera.Parameters params camera.getParameters(); // 设置预览尺寸 params.setPreviewSize(1920, 1080); // 设置图片尺寸 params.setPictureSize(4032, 3024); // 设置对焦模式 params.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE); // 应用参数 camera.setParameters(params);这个过程看似简单却隐藏着兼容性噩梦。setPreviewSize和setPictureSize必须使用getSupportedPreviewSizes()和getSupportedPictureSizes()返回的列表中的值但不同厂商的列表差异巨大。更棘手的是某些参数组合是不兼容的。例如你设置了某个高分辨率预览尺寸可能就会导致自动对焦模式不可用而API并不会明确告诉你只会在后续预览时出现异常或 silently fail静默失败。2.2 数据流处理SurfaceView与字节数组Camera1提供两种数据获取方式预览到Surface通过camera.setPreviewDisplay(surfaceHolder)或camera.setPreviewTexture(surfaceTexture)将预览画面直接渲染到SurfaceView或TextureView上。这是最常用、性能最好的方式因为数据直接传递给了显示系统无需经过应用层内存拷贝。预览回调通过camera.setPreviewCallback(...)注册回调每一帧预览数据都会以byte[]的形式回调到应用层。这给了你处理每一帧图像的能力如实时滤镜、人脸检测但代价巨大。将YUV或NV21格式的数据从Native层拷贝到Java层的字节数组是极其消耗CPU和内存的操作会迅速导致手机发烫和预览卡顿。注意很多初学者为了做实时处理滥用PreviewCallback结果导致应用性能崩溃。正确的做法是如果必须处理使用setPreviewCallbackWithBuffer并配合缓冲区复用或者直接使用setPreviewTexture并基于SurfaceTexture的OnFrameAvailableListener在Native层或RenderThread中进行处理避免跨层拷贝。2.3 Camera1的“历史包袱”与经典陷阱Camera1的设计在今天看来有诸多缺陷这些缺陷也成了我们开发中的经典陷阱参数设置的非原子性getParameters和setParameters不是原子操作。你在get之后可能其他线程或系统已经修改了摄像头状态导致你set的参数基于一个过时的状态从而引发不可预知的问题。在高动态场景如快速切换前后置摄像头下尤为明显。全局单例与生命周期管理Camera对象本身是一个重量级资源且同一时间只能有一个实例被打开。如果你的应用没有妥善管理例如在Activity的onPause中未释放Camera而另一个Activity又尝试打开就会导致崩溃。你必须严格遵守“谁打开谁释放”的原则并在onPause中确保释放。对焦与测光分离自动对焦(autoFocus)和自动测光(autoExposureLock)是分开的调用且流程复杂。要实现触摸对焦并显示对焦框你需要监听触摸事件将坐标从视图坐标系转换为摄像头传感器坐标系然后调用camera.autoFocus(callback)。这个过程涉及坐标变换容易出错。缺乏精细的元数据控制你无法单独控制曝光时间、ISO感光度、白平衡色温等底层参数只能通过有限的场景模式如夜景、运动来间接影响无法满足专业摄影或计算摄影的需求。尽管有这些缺陷Camera1因其广泛的兼容性直到Android 5.0都是主流至今仍是一些对兼容性要求极高、功能简单的场景下的备选方案。理解它是理解后续所有Camera API演进的基础。3. Camera2面向未来的重构与复杂性激增为了彻底解决Camera1的顽疾Android 5.0 (API 21) 引入了全新的Camera2 API (android.hardware.camera2)。它的设计哲学发生了根本性转变从以拍照为中心变为以数据流为中心从流程驱动变为异步事件驱动。它把摄像头抽象为一个可以向其发送捕获请求、并接收其捕获结果的设备更贴近现代摄像头的实际工作方式。3.1 核心架构管道模型与异步请求Camera2的核心是“管道”模型。你可以把它想象成一个高级餐厅的厨房CameraManager餐厅经理。负责列出所有可用的摄像头getCameraIdList并提供其能力菜单getCameraCharacteristics。CameraCharacteristics每个摄像头的“能力菜单”。里面详细列出了该摄像头支持的所有功能、分辨率范围、硬件等级等信息。在操作前你必须仔细研读这份菜单。CameraDevice具体的厨师摄像头硬件。通过openCamera获得。CaptureRequest顾客点的“菜谱”。你定义想要什么如输出到哪个Surface对焦模式曝光补偿。CaptureRequest.Builder菜谱的草稿纸。通过createCaptureRequest获得你可以往里面添加各种“食材”输出目标和“调味要求”参数。CameraCaptureSession一个已经建立好的、高效的“传菜通道”。它连接了CameraDevice和一组输出Surface比如预览的SurfaceView和拍照的ImageReader。创建Session是一个耗时操作。Surface装菜的“盘子”。可以是用于预览的SurfaceView/TextureView的Surface也可以是用于拍照的ImageReader的Surface甚至是用于录像的MediaRecorder的Surface。工作流程变为查询菜单 - 聘请厨师 - 准备盘子 - 建立传菜通道 - 不断发送菜谱请求 - 在对应的盘子里收到做好的菜图像数据。3.2 异步操作与状态回调Camera2的所有重要操作都是异步的通过回调通知结果。这避免了UI线程阻塞但大大增加了代码的复杂度。// 1. 获取CameraManager CameraManager manager (CameraManager) context.getSystemService(Context.CAMERA_SERVICE); // 2. 获取摄像头特性能力菜单 CameraCharacteristics characteristics manager.getCameraCharacteristics(cameraId); // 3. 异步打开摄像头 manager.openCamera(cameraId, new CameraDevice.StateCallback() { Override public void onOpened(NonNull CameraDevice camera) { // 摄像头已打开获得CameraDevice对象 mCameraDevice camera; // 4. 创建预览请求的Builder mPreviewRequestBuilder mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); // 5. 创建用于预览的Surface例如来自TextureView SurfaceTexture texture mTextureView.getSurfaceTexture(); texture.setDefaultBufferSize(previewSize.getWidth(), previewSize.getHeight()); Surface previewSurface new Surface(texture); mPreviewRequestBuilder.addTarget(previewSurface); // 将Surface加入请求目标 // 6. 创建CaptureSession传菜通道 ListSurface outputSurfaces Arrays.asList(previewSurface, mImageReader.getSurface()); mCameraDevice.createCaptureSession(outputSurfaces, new CameraCaptureSession.StateCallback() { Override public void onConfigured(NonNull CameraCaptureSession session) { mCaptureSession session; // 7. 设置重复请求开始预览 mPreviewRequest mPreviewRequestBuilder.build(); mCaptureSession.setRepeatingRequest(mPreviewRequest, null, null); } Override public void onConfigureFailed(NonNull CameraCaptureSession session) { // 配置失败处理 } }, null); } Override public void onDisconnected(NonNull CameraDevice camera) { /*...*/ } Override public void onError(NonNull CameraDevice camera, int error) { /*...*/ } }, mBackgroundHandler); // 必须指定一个后台Handler线程这段代码仅仅是建立预览就已经嵌套了多层回调。任何一个环节出错都需要在对应的错误回调中妥善处理否则会导致资源泄露或应用无响应。3.3 Camera2的优势与“高门槛”Camera2带来了巨大的灵活性提升并发数据流可以同时向预览Surface、拍照ImageReader和录像MediaRecorder发送数据轻松实现“边录边拍”。精细参数控制可以直接设置传感器曝光时间、ISO、镜头焦距等底层参数为专业模式铺平道路。更高效的缓冲区管理通过ImageReader获取图像缓冲区可以在Native层和Java层之间更高效地共享减少了内存拷贝。更丰富的元数据每一帧捕获结果都附带大量的元数据对焦状态、曝光状态、时间戳等便于进行高级图像处理。然而其复杂性也成了“高门槛”样板代码极多完成一个基础的预览拍照功能代码量是Camera1的5-10倍。异步错误处理复杂各种状态回调StateCallbackCaptureCallback分散在不同的地方错误处理链路长调试困难。设备兼容性仍需检查虽然接口统一但不同设备的CameraCharacteristics能力菜单差异巨大。你必须通过check方法动态判断是否支持某个特性如FLASH_MODE_OFFCONTROL_AF_MODE_CONTINUOUS_PICTURE不能想当然。性能调优点分散缓冲区大小、ImageReader的数量、请求模板TEMPLATE_PREVIEWTEMPLATE_RECORDTEMPLATE_STILL_CAPTURE的选择都影响着性能和功耗需要仔细权衡。4. CameraX谷歌的“和解方案”与最佳实践面对Camera2的复杂性以及Camera1的过时谷歌推出了Jetpack组件库中的CameraX。它的目标不是替代Camera2而是在其之上构建一个生命周期感知、用例驱动、向后兼容的抽象层。你可以把它理解为谷歌官方提供的、针对常见摄像头场景的“最佳实践套件”。4.1 核心概念用例、生命周期与选择器CameraX引入了几个关键概念极大地简化了开发用例将复杂的摄像头操作封装成几个明确的场景。PreviewView用于预览。它内部封装了TextureView或SurfaceView并自动处理尺寸变换和旋转。ImageCapture用于拍摄高画质照片。ImageAnalysis用于逐帧图像分析如二维码识别、人脸检测。它提供了ImageProxy对象比Camera1的PreviewCallback更高效。VideoCapture用于录制视频。生命周期绑定CameraX与LifecycleOwner如Activity/Fragment绑定。当生命周期处于STARTED状态时摄像头自动打开并运行当进入STOPPED状态时自动释放。你几乎不再需要手动管理open和release。CameraSelector一个优雅的选择器用于选择前后置摄像头而无需处理复杂的ID和特性查询。4.2 快速上手指南与代码对比使用CameraX实现一个带预览和拍照的功能代码简洁得令人惊讶// 1. 创建用例 val preview Preview.Builder().build() val imageCapture ImageCapture.Builder() .setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY) // 设置拍照模式最低延迟或最高质量 .build() // 2. 创建选择器选择后置摄像头 val cameraSelector CameraSelector.DEFAULT_BACK_CAMERA // 3. 将预览用例绑定到PreviewView布局中定义的视图 preview.setSurfaceProvider(previewView.surfaceProvider) // 4. 绑定生命周期 val cameraProviderFuture: ListenableFutureProcessCameraProvider ProcessCameraProvider.getInstance(context) cameraProviderFuture.addListener({ val cameraProvider: ProcessCameraProvider cameraProviderFuture.get() try { // 解绑所有用例 cameraProvider.unbindAll() // 绑定用例到生命周期和摄像头 val camera cameraProvider.bindToLifecycle( this as LifecycleOwner, // 生命周期所有者 cameraSelector, // 摄像头选择器 preview, // 预览用例 imageCapture // 拍照用例 ) // 现在预览已经自动开始拍照功能也已就绪 } catch (exc: Exception) { // 错误处理 } }, ContextCompat.getMainExecutor(context)) // 在主线程执行回调 // 5. 拍照 fun takePhoto() { val outputFileOptions ImageCapture.OutputFileOptions.Builder(File(...)).build() imageCapture.takePicture( outputFileOptions, ContextCompat.getMainExecutor(context), object : ImageCapture.OnImageSavedCallback { override fun onImageSaved(outputFileResults: ImageCapture.OutputFileResults) { // 拍照成功 } override fun onError(exception: ImageCaptureException) { // 拍照失败 } } ) }对比Camera2的数十行嵌套回调CameraX的代码清晰、线性且自动处理了绝大部分兼容性和生命周期问题。4.3 CameraX的适用场景与局限性CameraX是绝大多数应用开发者的首选但它并非万能优势极简API快速实现主流功能。自动兼容在底层自动选择Camera2或Camera1实现提供一致的API。设备一致性谷歌通过“一致性测试”确保不同设备上行为一致减少了碎片化问题。生命周期安全杜绝了资源泄露。局限性功能封装它封装了常见场景。如果你需要Camera2提供的极其精细的控制如手动设置精确到纳秒的曝光时间或者需要实现非常规的数据流组合CameraX可能无法直接满足需要回退到Camera2 API。性能开销作为抽象层它带来轻微的运行时开销。对于性能极限敏感的应用如超高帧率慢动作录制可能需要直接使用Camera2。新特性支持滞后最新的硬件特性如某些传感器独有的功能可能需要等待CameraX更新适配。实操心得对于90%的应用场景社交拍照、扫描、简单录像直接采用CameraX是最佳选择。从项目开始就引入CameraX能节省大量开发和调试时间。只有在明确需要CameraX不支持的专业级控制时才考虑直接使用Camera2。5. 实战中的共性难题与排查思路无论你选择哪个版本的API在实际开发中都会遇到一些共性的棘手问题。下面我将这些问题的排查思路梳理成一个链路你可以像查字典一样使用。5.1 问题一预览画面拉伸、变形或方向错误这是最常见的问题之一根本原因在于摄像头传感器输出、预览Surface尺寸、视图显示区域三者之间的宽高比和旋转角度不匹配。排查链路确定传感器输出尺寸通过CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE(Camera2) 或Camera.Parameters.getSupportedPreviewSizes()(Camera1) 获取。这是一个固定的物理像素矩阵。确定预览Surface尺寸这是你为预览分配缓冲区的尺寸。在Camera2中你需要从StreamConfigurationMap.getOutputSizes(SurfaceTexture::class.java)中选择在Camera1中从getSupportedPreviewSizes()中选择。关键点这个尺寸的宽高比应尽可能与上一步中传感器输出尺寸的宽高比一致以避免由硬件缩放引起的画质损失和额外功耗。确定视图显示尺寸你的PreviewView、TextureView或SurfaceView在屏幕上的实际宽高。计算与适配比例适配比较预览Surface尺寸和视图显示尺寸的宽高比。如果不同你需要决定是“填满”可能裁剪还是“适应”可能留黑边。CameraX的PreviewView默认提供了多种ScaleType如FIT_CENTERFILL_CENTER来自动处理。旋转适配通过CameraCharacteristics.SENSOR_ORIENTATION获取传感器自然方向通常是横屏方向。再结合设备当前物理方向和应用界面固定方向计算出需要旋转的度数并通过setDisplayOrientation(Camera1) 或设置CaptureRequest的JPEG_ORIENTATION/SCALER_CROP_REGION(Camera2) 来校正。CameraX的PreviewView和ImageCapture会自动处理旋转。注意很多开发者忽略了传感器方向直接设置显示旋转导致前置摄像头预览镜像错误。正确处理旋转和镜像逻辑是保证预览和成片方向正确的关键。5.2 问题二对焦失败、拍照延迟高对焦问题通常源于场景低对比度、低光照、硬件限制或参数配置不当。排查链路检查对焦模式支持首先确认选中的摄像头支持你要求的对焦模式如CONTINUOUS_PICTURE。通过CameraCharacteristics.CONTROL_AF_AVAILABLE_MODES(Camera2) 或Camera.Parameters.getSupportedFocusModes()(Camera1) 查询。分析场景在纯色墙面、黑暗环境或快速移动场景下任何对焦系统都可能失败。可以尝试切换到FOCUS_MODE_INFINITY或FOCUS_MODE_FIXED如果支持或者提示用户改善拍摄环境。Camera1特有陷阱确保在调用autoFocus之前预览已经启动。在setParameters后立即调用autoFocus可能会失败。Camera2的异步性在Camera2中对焦是一个状态机CONTROL_AF_STATE_*。发送一个对焦请求CONTROL_AF_TRIGGER_START后需要监听CaptureResult.CONTROL_AF_STATE的变化直到它进入FOCUSED或NOT_FOCUSED状态才能判定对焦完成。不能假设发送请求后立即就对焦好了。拍照延迟高延迟往往是因为使用了TEMPLATE_STILL_CAPTURE模式但未正确控制曝光。在低光下自动曝光可能需要较长时间。可以尝试在拍照前先锁定曝光CONTROL_AE_PRECAPTURE_TRIGGER或者使用TEMPLATE_ZERO_SHUTTER_LAG模式如果设备支持。5.3 问题三内存溢出与性能瓶颈摄像头是数据吞吐大户处理不当极易引起OOM和卡顿。排查链路审视数据路径避免Java层回调绝对不要在Camera1中使用PreviewCallback进行高频、复杂的图像处理。如果必须使用setPreviewCallbackWithBuffer并复用缓冲区。使用ImageReader在Camera2中使用ImageReader获取图像。通过ImageReader.newInstance()设置合适的maxImages参数通常2-3张即可并在OnImageAvailableListener中及时调用image.close()释放缓冲区。持有Image对象不释放是常见的内存泄漏源。选择合适分辨率不要盲目选择最高分辨率。预览使用1080P或720P通常足够分析用例根据算法需要选择最低可接受分辨率。管理生命周期确保在onPause或生命周期回调中及时关闭摄像头、停止预览、释放所有ImageReader和Surface。监控内存使用Android Profiler监控Java堆和Native堆内存。如果看到Native内存持续增长很可能是在Native层如MediaCodec Camera HAL有泄漏虽然你的Java代码可能已经释放了引用。线程管理Camera2的所有回调默认在Handler指定的线程执行。如果在这个回调中进行耗时操作如图像处理会阻塞后续的摄像头请求导致预览卡顿。务必确保回调方法快速返回将耗时任务抛到其他工作线程。6. 版本选择与架构设计建议面对三个主要的Camera API该如何选择这取决于你的应用目标、团队资源和设备覆盖要求。决策矩阵参考考量维度Camera1Camera2CameraXAPI 复杂度低极高低功能控制力弱受限极强可精细控制中等覆盖主流场景设备兼容性仅支持旧设备API 21支持 API 21但不同厂商实现差异大支持 API 21通过适配层提供一致行为开发速度快但已过时慢非常快维护成本低但功能有限高低推荐场景维护极其老旧的应用或仅需最基本功能且必须覆盖Android 4.x开发专业相机应用、需要极致性能控制、使用特殊硬件功能绝大多数现代应用、快速原型、希望降低碎片化影响架构设计建议 对于新项目我的强烈建议是以CameraX为默认和首选方案。它解决了95%的需求并且随着谷歌的维护其功能和稳定性会持续增强。在你的应用架构中可以将摄像头操作封装在一个独立的模块或ViewModel中。创建CameraController封装CameraX的初始化、用例绑定、生命周期控制、权限检查等逻辑。定义状态接口对外暴露简单的接口如startPreview()takePhoto(callback)switchCamera()setFlashMode(mode)。将复杂的CameraX API调用隐藏在内部。错误处理统一化在Controller内部将CameraX的各种异常CameraUnavailableExceptionImageCaptureException等转换为应用层能理解的统一错误码或消息通过LiveData或回调传递出去。为高级功能留出扩展口在Controller内部可以保留获取底层Camera2CameraControl和Camera2CameraInfoCameraX提供的扩展接口的能力。当未来需要实现CameraX未封装的高级功能时可以通过这些扩展接口直接操作部分Camera2能力而无需重构整个摄像头模块。这样的设计既享受了CameraX的开发效率与兼容性红利又为未来的功能扩展预留了可能性。摄像头开发不再是令人望而生畏的“深水区”而是一个可以被清晰管理和迭代的功能模块。