实战项目:自定义Camera应用开发

说实话,做Camera开发这么多年,我最大的感触就是——纸上得来终觉浅。你读再多文档,不如亲手写一个能跑起来的Camera应用。这一章,我们就来干点实在的。

我会带你从零开始,封装一套Camera2 API的工具类,实现几个常用滤镜,再聊聊性能调优的那些坑。最后,我会分享一些发布时的注意事项。嗯,这些都是我踩过的坑,你直接拿去用就好。

1. 项目结构设计

我个人习惯把Camera项目分成三层:

  • UI层:负责预览、拍照、滤镜切换的界面
  • 业务层:封装Camera2 API的核心逻辑
  • 渲染层:处理滤镜、特效等图像处理

为什么要分层?说白了,就是为了解耦。我在项目中遇到过好几次,因为UI和Camera逻辑混在一起,改个预览尺寸结果把拍照流程搞崩了。分层之后,各管各的,清爽很多。

核心思路:Camera2 API的封装,重点在于状态机的管理。CameraDevice、CameraCaptureSession、CaptureRequest这三个对象,它们的生命周期必须严格把控。

2. Camera2 API封装实战

先来看一段核心封装代码。我简化了一些异常处理,但保留了最关键的逻辑:

public class Camera2Helper {
    private CameraManager cameraManager;
    private CameraDevice cameraDevice;
    private CameraCaptureSession captureSession;
    private CaptureRequest.Builder previewRequestBuilder;
    
    // 打开相机
    public void openCamera(Context context, String cameraId, 
                           CameraDevice.StateCallback callback) {
        cameraManager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE);
        try {
            cameraManager.openCamera(cameraId, callback, null);
        } catch (CameraAccessException e) {
            // 我曾经在这里吃过亏——权限没申请就调用了
            e.printStackTrace();
        }
    }
    
    // 创建预览会话
    public void createPreviewSession(Surface surface) {
        try {
            previewRequestBuilder = cameraDevice.createCaptureRequest(
                CameraDevice.TEMPLATE_PREVIEW);
            previewRequestBuilder.addTarget(surface);
            
            cameraDevice.createCaptureSession(Arrays.asList(surface),
                new CameraCaptureSession.StateCallback() {
                    @Override
                    public void onConfigured(CameraCaptureSession session) {
                        captureSession = session;
                        // 开始预览
                        startPreview();
                    }
                }, null);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }
}

你想想看,这段代码里最容易出问题的地方在哪?

createCaptureSession的回调。如果Surface被销毁了才回调,那就会崩溃。我建议你在回调里加一个isSurfaceValid的判断。

3. 滤镜实现方案

滤镜的实现,我推荐用OpenGL ES。为什么?因为Camera2的预览数据是YUV格式,直接用CPU处理太慢了。用GPU做颜色矩阵变换,效率能提升好几倍。

来看一个简单的黑白滤镜实现:

// 片段着色器 - 黑白滤镜
private static final String FRAGMENT_SHADER_BLACK_WHITE = 
    "#version 300 es\n" +
    "precision mediump float;\n" +
    "in vec2 vTexCoord;\n" +
    "out vec4 fragColor;\n" +
    "uniform sampler2D sTexture;\n" +
    "void main() {\n" +
    "    vec4 color = texture(sTexture, vTexCoord);\n" +
    "    float gray = dot(color.rgb, vec3(0.299, 0.587, 0.114));\n" +
    "    fragColor = vec4(vec3(gray), color.a);\n" +
    "}\n";

这段代码的核心就是那个dot函数。它把RGB三个通道按人眼敏感度加权求和,得到灰度值。嗯,这里要注意,权重系数是有讲究的,不是随便取的。

小技巧:如果你想要复古风滤镜,可以在灰度基础上加一点棕色调。我试过,效果比纯黑白更讨喜。

4. 性能调优实战

性能调优,说白了就是跟帧率较劲。我总结了几条经验:

  • 预览尺寸别贪大:1080P就够了,4K预览对大多数手机来说都是负担
  • 减少Surface数量:每个Surface都会增加一次数据拷贝
  • 用ImageReader的setOnImageAvailableListener:别在主线程处理图像数据
  • 滤镜用GPU:CPU做滤镜,帧率直接掉到15fps以下

我曾经在一个项目里,为了追求画质,用了最大分辨率预览。结果在低端机上,预览画面卡得像幻灯片。后来改成自适应分辨率,问题就解决了。

避坑指南:我曾经在华为某款手机上遇到一个bug——预览画面绿屏。排查了半天,发现是SurfaceView的格式不匹配。Camera2要求Surface格式必须是ImageFormat.YUV_420_888,而SurfaceView默认是RGBA。记得在创建Surface时指定格式。

5. 发布注意事项

发布前,有几件事你必须检查:

检查项 说明
权限声明 CAMERA权限、WRITE_EXTERNAL_STORAGE(Android 10以下)
机型适配 不同厂商的Camera HAL实现有差异,建议做兼容性测试
内存泄漏 CameraDevice和CaptureSession必须及时释放
后台限制 Android 9+对后台Camera访问有限制

嗯,这里要特别提一下内存泄漏。CameraDevice是一个重量级对象,如果你在Activity销毁时没有关闭它,那下次打开相机就会失败。我建议在onPause里释放,在onResume里重新打开。

6. 知识体系总览

下面这张图,是我整理的本章知识结构。你可以把它当作一个检查清单,看看自己掌握了哪些:

Camera应用开发知识体系 Camera2 API封装 UI层 预览界面 拍照按钮 滤镜切换 业务层 CameraDevice管理 CaptureSession CaptureRequest 渲染层 OpenGL ES 滤镜着色器 YUV转RGB 性能调优 分辨率选择 帧率优化 发布注意事项 权限检查 机型适配

这张图把整个Camera应用开发分成了四个模块。你从UI层入手,逐步深入到业务层和渲染层,最后再回头做性能调优和发布检查。这个顺序,是我个人觉得最不容易出错的路径。

好了,这一章的内容就到这里。代码和思路都给你了,剩下的就是动手去写。记住,Camera开发没有捷径,多踩坑才能成长。


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