4、Camera API 1详解:Camera类、预览流程、拍照流程、参数设置、回调机制、局限性分析

Camera API 1(即android.hardware.Camera)是Android自API Level 1起提供的原始相机接口,虽然Google在API 21之后推出了Camera 2,但API 1凭借其简单直接、兼容性广的特点,至今仍被大量存量应用和低端设备使用。理解API 1的完整链路——从Camera对象创建、预览循环、拍照触发、参数控制到回调线程模型——是掌握Android相机框架的基石。本章将逐层拆解API 1的每个关键环节,并重点分析其设计上的结构性局限,帮助开发者在选型与维护中做出正确判断。

Camera API 1 核心链路与回调模型 Android App Camera.open() / takePicture() Camera 类 核心API入口 预览流程 setPreviewDisplay + startPreview 拍照流程 takePicture → JPEG回调 参数设置 Camera.Parameters 回调机制 PreviewCallback / ShutterCallback 局限性分析 无RAW/无多摄/无HDR 关键回调线程模型 • 所有回调(onPreviewFrame / onPictureTaken)运行在Camera线程(非UI线程) • 回调中禁止执行耗时操作,否则会阻塞后续帧处理 • 必须在onPause中释放Camera,否则其他应用无法使用相机

4.1 Camera类:从打开到释放的生命周期管理

核心原理

android.hardware.Camera是API 1的核心,它直接映射到底层相机硬件驱动。Camera对象通过Camera.open(int cameraId)获取,其中cameraId对应后置(0)或前置(1)摄像头。打开成功后,应用必须显式调用setPreviewDisplay(SurfaceHolder)绑定预览Surface,再调用startPreview()启动预览循环。整个生命周期遵循“打开→配置→预览→拍照→释放”的严格顺序,任何一步遗漏都会导致异常或资源泄漏。

方法作用注意事项
Camera.open()获取相机独占访问权若被其他进程占用会抛出RuntimeException
getParameters()获取当前参数快照返回的是副本,修改后需setParameters提交
setParameters()应用参数变更参数不合法时抛出RuntimeException
setPreviewDisplay()绑定预览Surface必须在startPreview之前调用
startPreview()启动预览帧流重复调用会报错
takePicture()触发拍照拍照期间预览会暂停
release()释放硬件资源必须在onPause中调用,否则后续应用无法打开相机

操作流程

  1. 检查Camera.getNumberOfCameras()确认设备可用摄像头数量。
  2. 调用Camera.open(cameraId),建议放在try-catch中处理相机被占用的情况。
  3. 创建SurfaceView并实现SurfaceHolder.Callback,在surfaceCreated中调用setPreviewDisplay。
  4. 在surfaceChanged中设置预览尺寸并调用startPreview()。
  5. 在onPause中必须调用stopPreview()和release(),并将Camera对象置为null。
风险提醒:Camera API 1的open()是同步阻塞操作,在低端设备上可能耗时数百毫秒。若在UI线程调用,会导致界面卡顿甚至ANR。建议在子线程中执行open操作,但需注意Camera对象不能跨线程传递(线程亲和性限制)。

案例拆解:相机被占用时的优雅降级

某社交应用在用户点击拍照时,发现部分机型(如华为Mate系列)偶发“相机被占用”错误。通过日志分析,发现是上一个Activity未在onPause中释放Camera导致。修复方案:在onPause中增加camera.release()并置空引用,同时在onResume中重新打开。此外,增加Camera.open()的异常捕获,当捕获到RuntimeException时提示用户“相机被其他应用占用”,并引导用户关闭后台应用。修复后,崩溃率从0.3%降至0.02%。

常见误区

  • 误区1:认为release()后Camera对象还能继续使用。实际上,release后所有方法调用都会抛出异常。
  • 误区2:在surfaceDestroyed中忘记停止预览。这会导致Surface已销毁但Camera仍在运行,引发IllegalStateException。
  • 误区3:多个线程同时访问Camera对象。API 1不是线程安全的,必须通过同步块或Handler串行化访问。

4.2 预览流程:从Surface到屏幕的像素管道

核心原理

预览流程的本质是:Camera硬件传感器采集图像→ISP处理→输出YUV帧→通过SurfaceFlinger合成显示。API 1中,预览帧默认输出到SurfaceView绑定的Surface上。开发者可以通过setPreviewCallback获取每一帧的YUV数据(NV21格式),用于人脸检测、二维码扫描等实时分析。预览帧率通常为30fps,但受限于传感器能力和CPU负载。

操作流程

// 标准预览启动代码
Camera camera = Camera.open(0);
Camera.Parameters params = camera.getParameters();
params.setPreviewSize(1280, 720);
camera.setParameters(params);
camera.setPreviewDisplay(surfaceHolder);
camera.startPreview();
// 设置回调获取帧数据
camera.setPreviewCallback(new Camera.PreviewCallback() {
    @Override
    public void onPreviewFrame(byte[] data, Camera camera) {
        // data为NV21格式的YUV数据
        // 注意:此回调运行在Camera线程,不能做耗时操作
    }
});

案例拆解:二维码扫描的帧率优化

某物流App需要连续扫描快递单上的二维码。初始实现中,onPreviewFrame内直接调用ZXing库解码,导致帧率从30fps骤降至8fps,且UI卡顿。优化方案:

  1. 将预览分辨率从1920×1080降至1280×720,减少单帧数据量。
  2. 在onPreviewFrame中仅拷贝帧数据到byte[]缓冲区,通过Handler发送到解码线程。
  3. 解码线程使用BlockingQueue接收帧,并丢弃积压的旧帧(只处理最新帧)。
  4. 增加帧间隔控制:若解码耗时超过100ms,则跳过后续帧直到解码完成。

优化后,帧率稳定在25fps,解码成功率从82%提升至97%。关键经验:预览回调中绝不能做耗时操作,必须将帧数据拷贝后异步处理。

常见误区

  • 误区1:在onPreviewFrame中直接调用camera.startPreview()重启预览。这会导致死循环和性能崩溃。
  • 误区2:忽略预览尺寸与Surface尺寸的适配。若两者比例不一致,画面会被拉伸变形。
  • 误区3:未处理surfaceChanged中的横竖屏切换。旋转屏幕时需重新设置预览尺寸并调用startPreview()。

4.3 拍照流程:从快门到JPEG的完整链路

核心原理

拍照流程比预览复杂得多。调用takePicture(ShutterCallback, RawCallback, PostViewCallback, JpegCallback)后,Camera会依次触发:快门回调(播放快门音)→原始数据回调(可选)→压缩JPEG回调。JPEG回调返回的是完整的图片字节数组,可直接保存到文件或上传服务器。拍照期间预览会暂停,直到JPEG回调返回后自动恢复。

回调类型触发时机典型用途
ShutterCallback快门打开的瞬间播放快门音效
RawCallback原始RAW数据可用时专业摄影(API 1中极少使用)
PostViewCallback预览帧更新时快速预览(已废弃)
JpegCallbackJPEG压缩完成时保存图片/上传

操作流程

  1. 拍照前设置Camera.Parameters:setPictureSize()、setJpegQuality(100)、setPictureFormat(ImageFormat.JPEG)。
  2. 调用takePicture(),传入四个回调(可为null)。
  3. 在JpegCallback.onPictureTaken(byte[] data, Camera camera)中保存data到文件。
  4. 保存完成后,调用camera.startPreview()恢复预览。
经验提示:拍照后必须手动调用startPreview(),否则预览会一直停留在最后一帧。部分开发者忘记此步骤,导致界面“冻结”,这是API 1最常见的Bug之一。

案例拆解:连拍功能的实现与性能瓶颈

某运动相机App需要实现每秒3张的连拍功能。初始实现中,每次拍照后等待JPEG回调完成再启动下一次拍照,实际连拍速度仅0.8张/秒。分析发现瓶颈在于JPEG压缩耗时(约300ms)和文件写入耗时(约200ms)。优化方案:

  • 将JPEG质量从100降至90,压缩时间降至180ms。
  • 使用BufferedOutputStream异步写入文件,避免阻塞Camera线程。
  • 在onPictureTaken中不立即保存,而是将data放入内存队列,由后台线程批量写入。

优化后连拍速度提升至2.5张/秒。但进一步提速遇到硬件瓶颈——传感器曝光和ISP处理时间无法压缩。最终方案是采用Camera 2的BurstCapture模式,才真正实现10fps连拍。此案例说明:API 1的拍照链路存在结构性延迟,不适合高速连拍场景。

4.4 参数设置:Camera.Parameters的完整操作指南

核心原理

Camera.Parameters是一个键值对集合,通过getParameters()获取当前配置的副本,修改后调用setParameters()提交。参数分为几大类:预览参数(尺寸、帧率)、图片参数(分辨率、JPEG质量)、对焦参数(模式、区域)、白平衡、曝光补偿、场景模式等。不同厂商对参数的支持程度不同,需通过getSupportedPreviewSizes()等方法查询能力范围。

常用参数速查表

参数类别关键方法典型值
预览尺寸setPreviewSize(int w, int h)1280×720, 1920×1080
图片尺寸setPictureSize(int w, int h)4032×3024(12MP)
JPEG质量setJpegQuality(int q)85-100
对焦模式setFocusMode(String)FOCUS_MODE_AUTO / CONTINUOUS_PICTURE
白平衡setWhiteBalance(String)WHITE_BALANCE_AUTO / DAYLIGHT
曝光补偿setExposureCompensation(int)-12 到 +12
场景模式setSceneMode(String)SCENE_MODE_NIGHT / SPORTS

操作流程与注意事项

// 安全设置参数的模板
Camera.Parameters params = camera.getParameters();
// 检查是否支持目标尺寸
List<Camera.Size> sizes = params.getSupportedPictureSizes();
Camera.Size bestSize = sizes.get(0);
for (Camera.Size size : sizes) {
    if (size.width == 4032 && size.height == 3024) {
        bestSize = size;
        break;
    }
}
params.setPictureSize(bestSize.width, bestSize.height);
// 设置对焦模式
if (params.getSupportedFocusModes().contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) {
    params.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);
}
// 提交参数
camera.setParameters(params);
风险提醒:直接设置不支持的参数会抛出RuntimeException。务必先查询getSupported*系列方法,再设置参数。此外,setParameters()是同步操作,频繁调用会阻塞Camera线程。

常见误区

  • 误区1:修改参数后不调用setParameters(),导致参数不生效。
  • 误区2:在预览运行中修改预览尺寸,需要先stopPreview()再设置参数,最后startPreview()。
  • 误区3:忽略不同机型的参数差异。例如,部分低端机不支持1080p预览,需降级到720p。

4.5 回调机制:线程模型与性能陷阱

核心原理

API 1的回调全部运行在Camera线程(由系统创建),而非UI线程。这意味着回调中不能直接更新UI,必须通过Handler切换到主线程。同时,回调的执行是串行的——如果某个回调耗时过长,会阻塞后续所有回调的触发。例如,onPreviewFrame中若执行了200ms的耗时操作,预览帧率会直接降至5fps。

回调线程模型图

Camera线程负责:预览帧回调、拍照回调、自动对焦回调。UI线程负责:SurfaceView渲染、用户交互。两者通过Handler桥接。

案例拆解:人脸检测的帧率优化

某美颜App需要实时检测人脸位置。初始实现中,onPreviewFrame内直接调用Google ML Kit的人脸检测器,单帧耗时约150ms,导致预览严重卡顿。优化方案:

  1. 在onPreviewFrame中仅拷贝帧数据(约2ms),通过HandlerThread发送到独立线程。
  2. 检测线程使用Semaphore控制并发:若上一帧还在检测,则丢弃当前帧。
  3. 检测结果通过runOnUiThread更新到UI。

优化后,预览帧率恢复至30fps,检测延迟控制在100ms以内。关键经验:回调中只做“轻量操作”,重活必须交给后台线程。

常见误区

  • 误区1:在回调中直接创建对象(如Bitmap),导致频繁GC和卡顿。
  • 误区2:忘记在onPause中移除回调(setPreviewCallback(null)),导致Activity泄漏。
  • 误区3:多个回调同时修改共享状态,引发线程安全问题。

4.6 局限性分析:为什么API 1终将被取代

核心原理

API 1的设计基于2008年的硬件水平,存在以下结构性缺陷:

局限性具体表现影响
无RAW输出仅支持YUV和JPEG无法进行专业后期处理
单摄像头无法同时操作多摄不支持背景虚化、广角切换
无手动控制ISO、快门速度不可调夜景拍摄效果差
回调线程阻塞耗时操作会拖垮预览实时处理能力受限
无HDR/夜景模式依赖厂商扩展接口功能碎片化严重
生命周期管理脆弱release不当导致崩溃应用稳定性风险高

实战场景:何时仍应选择API 1

尽管有诸多局限,API 1在以下场景中仍有价值:

  • 兼容性优先:需要支持Android 4.x及以下的老设备。
  • 简单需求:仅需拍照和预览,无需复杂控制。
  • 快速开发:原型验证或内部工具,API 1的开发效率更高。

迁移建议

对于新项目,强烈建议直接使用Camera 2或CameraX。若现有代码基于API 1,可逐步迁移:先封装Camera操作接口,再替换为CameraX的PreviewView和ImageCapture,最后移除API 1依赖。

总结复盘:API 1的核心价值在于其简单性,但代价是功能受限和性能瓶颈。开发者需要根据目标设备范围和应用场景做出权衡。若追求极致画质和功能,必须拥抱Camera 2;若仅需基础拍照,API 1仍可胜任。无论选择哪种方案,理解Camera生命周期和回调线程模型都是Android相机开发的基本功。