Camera1 实战:拍照功能实现

好,咱们直接进入正题。Camera1 虽然老,但市面上大量设备还在用。你想想看,很多定制 ROM、低端机、甚至一些工业平板,Camera1 依然是主力。我个人习惯是,不管新 API 多香,Camera1 这套基本功必须扎实。

1. 拍照流程概览

拍照这件事,说白了就三步:打开相机、预览画面、拍下来。但中间藏着不少坑。我画了张图,你先看个全貌。

Camera1 拍照核心流程 1. 打开相机 (open()) 2. 设置参数 (Parameters) 3. 启动预览 (startPreview()) 4. 拍照 (takePicture()) 5. 回调中保存图片

嗯,这里要注意:拍照前必须保证预览已经启动。我在项目中遇到过新手直接调 takePicture() 然后黑屏,就是因为预览没开。

2. 打开相机与预览

打开相机很简单,但有个细节——一定要在子线程里做。Camera.open() 是耗时操作,放主线程会卡界面。

private Camera mCamera;

private void openCamera() {
    try {
        mCamera = Camera.open(); // 默认打开后置
    } catch (Exception e) {
        e.printStackTrace();
        // 我曾经遇到过某些设备 Camera.open() 返回 null
        // 所以最好加个判空
    }
}

private void startPreview(SurfaceHolder holder) {
    try {
        mCamera.setPreviewDisplay(holder);
        mCamera.startPreview();
    } catch (IOException e) {
        e.printStackTrace();
    }
}
⚠️ 注意:Camera 对象必须与 SurfaceHolder 绑定。如果 Surface 被销毁了,记得停止预览并释放 Camera。否则下次打开会报错。

3. 自动对焦

自动对焦是拍照的命门。你想想看,一张照片糊了,其他参数再好也没用。Camera1 的对焦模式有好几种,我列个表给你看。

对焦模式 适用场景 说明
FOCUS_MODE_AUTO 普通拍照 单次对焦,触发后锁定
FOCUS_MODE_CONTINUOUS_PICTURE 连拍/抓拍 持续对焦,适合移动物体
FOCUS_MODE_CONTINUOUS_VIDEO 视频录制 平滑对焦,避免频繁跳动
FOCUS_MODE_FIXED 固定距离 不支持自动对焦的设备用

我个人习惯用 CONTINUOUS_PICTURE 作为默认。但要注意,不是所有设备都支持。所以设置前一定要检查一下。

private void setupAutoFocus() {
    Camera.Parameters params = mCamera.getParameters();
    List<String> supportedModes = params.getSupportedFocusModes();
    
    if (supportedModes != null) {
        if (supportedModes.contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) {
            params.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);
        } else if (supportedModes.contains(Camera.Parameters.FOCUS_MODE_AUTO)) {
            params.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
        }
    }
    mCamera.setParameters(params);
}
💡 小技巧:如果设备不支持你设置的模式,setParameters() 不会抛异常,但会静默失败。所以最好先查一下 supportedModes。

4. 触摸对焦

触摸对焦是用户交互的关键。说白了就是用户点哪里,相机就对焦到哪里。Camera1 里通过 autoFocus() 配合 MeteringAreas 实现。

嗯,这里有个坑:MeteringAreas 只在 API 14+ 支持。低版本设备只能做简单的中心对焦。

public void onTouchFocus(Rect touchRect) {
    Camera.Parameters params = mCamera.getParameters();
    
    if (params.getMaxNumMeteringAreas() > 0) {
        List<Camera.Area> areas = new ArrayList<>();
        areas.add(new Camera.Area(touchRect, 1000));
        params.setMeteringAreas(areas);
    }
    
    if (params.getMaxNumFocusAreas() > 0) {
        List<Camera.Area> focusAreas = new ArrayList<>();
        focusAreas.add(new Camera.Area(touchRect, 1000));
        params.setFocusAreas(focusAreas);
    }
    
    mCamera.setParameters(params);
    mCamera.autoFocus(new Camera.AutoFocusCallback() {
        @Override
        public void onAutoFocus(boolean success, Camera camera) {
            // success 表示对焦是否成功
            // 我曾经遇到过某些设备永远返回 false
            // 但实际画面是清晰的,所以别太依赖这个回调
        }
    });
}
🔑 关键点:触摸对焦的 Rect 需要做坐标映射。屏幕坐标和相机传感器坐标是反的,而且比例不同。记得用 camera.autoFocus() 触发实际对焦动作。

5. 闪光灯控制

闪光灯控制其实就三种模式:开、关、自动。但实现上有个小陷阱——拍照时闪光灯和预览时闪光灯是两回事

模式 预览时 拍照时
FLASH_MODE_OFF 不亮 不闪
FLASH_MODE_ON 常亮(部分设备) 拍照时闪
FLASH_MODE_AUTO 不亮 根据环境自动
FLASH_MODE_TORCH 常亮(手电筒模式) 常亮
private void setFlashMode(String mode) {
    Camera.Parameters params = mCamera.getParameters();
    List<String> supportedFlashModes = params.getSupportedFlashModes();
    
    if (supportedFlashModes != null && supportedFlashModes.contains(mode)) {
        params.setFlashMode(mode);
        mCamera.setParameters(params);
    }
}

// 使用示例
setFlashMode(Camera.Parameters.FLASH_MODE_AUTO);
⚠️ 注意:TORCH 模式会持续点亮闪光灯,非常耗电。我在项目中遇到过用户开着 TORCH 拍视频,手机发烫严重。建议只在需要补光时临时开启。

6. 相机参数调节

相机参数调节是 Camera1 最灵活也最麻烦的地方。常用的参数有:

  • 图片大小:用 getSupportedPictureSizes() 获取,选一个最接近屏幕比例的
  • 预览大小:同样用 getSupportedPreviewSizes(),注意要和 Surface 比例匹配
  • 白平衡:AUTO、DAYLIGHT、FLUORESCENT 等
  • ISO:部分设备支持,低光场景有用
  • 曝光补偿:范围一般是 -3 到 +3,步长 0.5
private void setupParameters() {
    Camera.Parameters params = mCamera.getParameters();
    
    // 设置图片大小
    Camera.Size pictureSize = getOptimalSize(
        params.getSupportedPictureSizes(), 1920, 1080);
    params.setPictureSize(pictureSize.width, pictureSize.height);
    
    // 设置预览大小
    Camera.Size previewSize = getOptimalSize(
        params.getSupportedPreviewSizes(), 1280, 720);
    params.setPreviewSize(previewSize.width, previewSize.height);
    
    // 设置白平衡
    params.setWhiteBalance(Camera.Parameters.WHITE_BALANCE_AUTO);
    
    // 设置曝光补偿
    int maxExposure = params.getMaxExposureCompensation();
    int minExposure = params.getMinExposureCompensation();
    if (maxExposure != 0 || minExposure != 0) {
        params.setExposureCompensation(0); // 默认0
    }
    
    mCamera.setParameters(params);
}

private Camera.Size getOptimalSize(List<Camera.Size> sizes, int w, int h) {
    // 这里简单实现:选第一个支持的
    // 实际项目中需要根据比例和分辨率做匹配
    return sizes.get(0);
}
💡 个人经验:设置参数时,最好一次性把所有参数都设好,再调用 setParameters()。频繁调用 setParameters() 会导致预览卡顿。我一般会在初始化时统一设置一次。

7. 拍照与保存

最后一步,拍照。调用 takePicture() 后,会触发三个回调:

  1. onShutter():快门声响起时调用
  2. onRawPictureAvailable():原始数据回调(可选)
  3. onJpegPicture():JPEG 数据回调,在这里保存图片
private void takePhoto() {
    mCamera.takePicture(new Camera.ShutterCallback() {
        @Override
        public void onShutter() {
            // 播放快门音效
        }
    }, null, new Camera.PictureCallback() {
        @Override
        public void onPictureTaken(byte[] data, Camera camera) {
            // 保存图片到文件
            File pictureFile = new File(Environment
                .getExternalStorageDirectory(), "photo.jpg");
            try {
                FileOutputStream fos = new FileOutputStream(pictureFile);
                fos.write(data);
                fos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            
            // 拍照后预览会停止,需要重新启动
            camera.startPreview();
        }
    });
}
🔑 关键点:拍照后一定要重新调用 startPreview(),否则画面会卡住。我曾经见过有人忘了这步,结果拍一张照片后预览就黑了。

8. 释放资源

最后,别忘了释放 Camera。不释放的话,其他应用就打不开相机了。

private void releaseCamera() {
    if (mCamera != null) {
        mCamera.stopPreview();
        mCamera.release();
        mCamera = null;
    }
}
⚠️ 注意:release() 必须在主线程调用?其实不是,但建议在子线程做。另外,释放后 Camera 对象就失效了,不要再调用任何方法。

好了,Camera1 拍照的核心功能就这些。说白了就是:打开、设参、预览、对焦、拍照、释放。每一步都有细节,但掌握了这些,你就能应付大部分场景了。


公众号:蓝海资料掘金营,微信deep3321