8. Camera1 + SurfaceView:从零搭建预览通道

各位同学,今天我们来聊聊 Android 上最经典的相机方案——Camera1 + SurfaceView。说实话,Camera1 这个 API 已经有些年头了,Google 官方也早就推荐大家用 Camera2。但为什么我还要专门讲它?

原因很简单:存量市场太大。你出去面试,十家公司有八家还在用 Camera1 做基础功能。而且 Camera1 的流程清晰,特别适合理解「相机 -> 预览 -> 数据回调」这条主线。把 Camera1 吃透了,后面学 Camera2 会轻松很多。

核心结论:Camera1 虽然老,但它是理解 Android 相机架构的最佳入口。我个人的经验是,先啃下 Camera1,再去看 Camera2 的复杂状态机,思路会顺很多。

8.1 Camera1 API 概览

Camera1 的 API 设计非常直观。说白了,就是三个核心类:

  • Camera:代表物理摄像头,负责打开、配置、预览、拍照。
  • Camera.Parameters:配置参数,比如预览尺寸、对焦模式、闪光灯等。
  • SurfaceView:显示预览画面的载体,需要配合 SurfaceHolder 使用。

你可能会问:为什么不用 ImageView 或者普通的 View?嗯,这里有个关键点——SurfaceView 拥有独立的绘图表面。它可以在子线程里直接渲染,不会阻塞主线程的 UI 更新。相机预览是高频数据流,如果用普通 View,界面很容易卡顿。

我的习惯:只要涉及相机预览,我永远优先选择 SurfaceView。TextureView 虽然也能用,但兼容性问题多一些,尤其是低端机。

8.2 Camera 预览流程

整个预览流程,我把它拆成 5 步。你照着这个顺序写,基本不会出错:

  1. 获取 Camera 实例:调用 Camera.open(),记得捕获异常。
  2. 配置参数:拿到 Camera.Parameters,设置预览尺寸、帧率等。
  3. 绑定 Surface:通过 setPreviewDisplay() 把 SurfaceHolder 传进去。
  4. 开始预览:调用 startPreview()
  5. 停止并释放:在合适的时机调用 stopPreview()release()

这里我画了一张流程图,帮你把整个链路串起来:

Camera.open() 配置 Parameters setPreviewDisplay() startPreview() 预览中... stopPreview() release()

你看,流程并不复杂。但有几个坑,我当年踩得特别惨。

我曾经犯过的错:surfaceCreated() 回调里直接打开相机并开始预览。结果发现,如果用户快速切换页面再回来,相机还没释放完,第二次打开就会崩溃。后来我加了一个标志位,确保释放完成后再打开。

8.3 设置预览尺寸

预览尺寸是 Camera1 里最容易出问题的地方。为什么?因为 Camera 支持的预览尺寸是有限的,而且不同手机差异巨大。

我建议你这样做:

Camera.Parameters params = camera.getParameters();
List<Camera.Size> supportedSizes = params.getSupportedPreviewSizes();

// 选择一个最接近目标尺寸的
Camera.Size bestSize = getBestSize(supportedSizes, targetWidth, targetHeight);
params.setPreviewSize(bestSize.width, bestSize.height);
camera.setParameters(params);

这里有个细节:getBestSize() 怎么写?我的做法是遍历所有支持的尺寸,计算宽高比与目标比例的差值,再比较面积。优先保证比例一致,再选分辨率最高的。

一个小技巧:很多同学直接写死 1920x1080,结果在旧手机上预览黑屏。我习惯先调用 getSupportedPreviewSizes() 打印日志,看看设备支持哪些尺寸,再动态选择。

8.4 拍照与回调

拍照的逻辑其实就三行代码:

camera.takePicture(null, null, new Camera.PictureCallback() {
    @Override
    public void onPictureTaken(byte[] data, Camera camera) {
        // data 就是 JPEG 数据
        // 可以保存到文件,或者上传
        camera.startPreview(); // 拍照后必须重新开始预览
    }
});

但这里有个容易忽略的点:拍照后预览会停止。如果你不手动调用 startPreview(),画面就会卡住。我见过不少新手在这里翻车。

另外,takePicture() 的第一个参数是 ShutterCallback,第二个是 RawCallback。一般我们用不到 raw 数据,传 null 就行。但 ShutterCallback 可以用来播放快门音效,提升用户体验。

我的经验:拍照回调里的 data 是 JPEG 字节数组,可以直接写入文件。但如果你需要做实时滤镜或者人脸检测,建议用 setPreviewCallback() 获取 NV21 格式的帧数据,那个效率更高。

8.5 完整代码骨架

最后,我给你一个最精简的代码骨架。你把它复制到项目里,改改就能跑:

public class CameraPreviewActivity extends AppCompatActivity
        implements SurfaceHolder.Callback {

    private Camera camera;
    private SurfaceHolder holder;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_camera);

        SurfaceView surfaceView = findViewById(R.id.surface_view);
        holder = surfaceView.getHolder();
        holder.addCallback(this);
    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        try {
            camera = Camera.open();
            Camera.Parameters params = camera.getParameters();
            // 设置预览尺寸(建议动态选择)
            params.setPreviewSize(1280, 720);
            camera.setParameters(params);
            camera.setPreviewDisplay(holder);
            camera.startPreview();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
        // 如果预览尺寸或方向改变,可以在这里处理
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        if (camera != null) {
            camera.stopPreview();
            camera.release();
            camera = null;
        }
    }

    public void takePhoto() {
        if (camera != null) {
            camera.takePicture(null, null, (data, cam) -> {
                // 保存 data 到文件
                cam.startPreview();
            });
        }
    }
}

注意:Camera1 的 open()takePicture() 都需要在子线程调用吗?其实不需要,Camera1 的 API 设计就是同步的,你可以在主线程直接调用。但 onPictureTaken 回调是在子线程执行的,所以别在里面更新 UI,记得切回主线程。

好了,这一章的内容就到这里。Camera1 虽然老,但它是理解 Android 相机的基础。你把这个流程跑通了,后面学 Camera2 的复杂状态机时,会发现很多概念都是相通的。


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