【Android 美颜相机】第四天:CameraLoader、Camera1Loader 与 Camera2Loader

CameraLoader、Camera1Loader 与 Camera2Loader

引言

在 Android 基于 GPUImage 框架 实现「相机实时预览 + GPU 滤镜渲染」的核心能力中,CameraLoaderCamera1LoaderCamera2Loader底层相机数据采集的核心类,也是 GPUImage 相机模块的基石。

这三个类的设计完全遵循 「面向接口编程」 的经典设计思想,其核心价值是:对上层开发者屏蔽 Android 原生两套相机 API 的底层差异,提供统一的相机操作规范

  • CameraLoader:是顶层抽象接口,仅定义相机操作的通用方法与回调规范,无任何具体实现,是所有相机加载器的「行为标准」;
  • Camera1Loader:是 CameraLoader 的标准实现类,底层完整封装 Android 原生废弃的 Camera1 API(android.hardware.Camera),适配低版本设备;
  • Camera2Loader:是 CameraLoader 的高级实现类,底层完整封装 Android 原生推荐的 Camera2 API(android.hardware.camera2),适配高版本设备,支持高级相机能力。

本文的核心是 「源码层面的深度剖析」 ------ 全文以 GPUImage 框架的原生源码为核心,逐类、逐方法、逐回调解析完整源码,讲解每个类的设计初衷、核心逻辑、成员变量作用、方法实现原理,以及三者之间的源码关联与设计差异。

看懂本文,你将彻底掌握 GPUImage 相机加载器的底层实现,也能理解 Android 两套相机 API 的封装逻辑与使用精髓。

CameraLoader 顶层抽象接口 「完整源码+全解析」

1 类定位与设计初衷

jp.co.cyberagent.android.gpuimage.camera.CameraLoader所有相机加载器的父接口 ,无父类、无成员变量、无具体实现,仅包含抽象方法与内部回调接口。
设计初衷

  1. 统一相机的「初始化、开启预览、停止预览、释放资源、切换摄像头」等核心操作的调用方式,让上层业务代码无需关心底层是 Camera1 还是 Camera2 实现;
  2. 统一相机预览帧数据的回调格式,让 GPUImageView 能无缝接收不同相机加载器的帧数据,实现「相机采集」与「滤镜渲染」的解耦;
  3. 统一相机的生命周期状态回调,让上层能统一处理相机的成功、失败、异常等状态。

2 CameraLoader 完整原生源码(无删减)

java 复制代码
package jp.co.cyberagent.android.gpuimage.camera;

import android.content.Context;
import android.util.Size;

/**
 * GPUImage相机加载器的顶层抽象接口
 * 定义所有相机加载器必须实现的通用方法与回调,是相机操作的统一规范
 */
public interface CameraLoader {

    /**
     * 初始化相机,绑定上下文与相机ID
     * @param context 上下文对象,建议传入ApplicationContext防止内存泄漏
     * @param cameraId 相机ID:0=后置摄像头,1=前置摄像头
     */
    void setUp(Context context, int cameraId);

    /**
     * 开启相机预览,核心方法
     * 调用后相机开始采集帧数据,并通过OnPreviewFrameListener回调输出
     */
    void startPreview();

    /**
     * 停止相机预览
     * 暂停帧数据采集,但不释放相机核心资源,可重新调用startPreview恢复预览
     */
    void stopPreview();

    /**
     * 释放相机所有资源【重中之重】
     * 释放相机实例、回调、缓冲区等所有资源,页面销毁时必须调用,防止内存泄漏
     * 调用后相机加载器无法再复用,需重新调用setUp初始化
     */
    void release();

    /**
     * 切换前后摄像头
     * @param cameraId 目标相机ID:0=后置,1=前置
     */
    void switchCamera(int cameraId);

    /**
     * 设置相机预览尺寸
     * 预览尺寸必须与GPUImageView的渲染尺寸匹配,否则会出现画面拉伸/变形
     * @param previewSize 预览尺寸对象(宽高)
     */
    void setPreviewSize(Size previewSize);

    /**
     * 设置预览帧数据回调【核心通信桥梁】
     * 相机采集到的每一帧YUV数据,都会通过该回调传递给上层(GPUImageView)
     * @param listener 预览帧回调监听器
     */
    void setOnPreviewFrameListener(OnPreviewFrameListener listener);

    /**
     * 设置相机状态回调
     * 监听相机的开启、关闭、异常等生命周期状态,用于上层异常处理
     * @param listener 相机状态回调监听器
     */
    void setCameraListener(CameraListener listener);

    // ====================================== 核心内部回调接口 ======================================
    /**
     * 预览帧数据回调接口:相机 → GPUImageView 的核心数据通道
     * 所有相机加载器的帧数据,都通过该接口以统一格式输出,是滤镜渲染的唯一数据来源
     */
    interface OnPreviewFrameListener {
        /**
         * 每一帧预览数据的回调方法
         * @param data 预览帧原始数据,固定为 YUV_420_888 格式的 byte[] 数组,Android相机标准格式
         * @param width 预览帧的宽度
         * @param height 预览帧的高度
         */
        void onPreviewFrame(byte[] data, int width, int height);
    }

    /**
     * 相机状态回调接口:相机生命周期的统一通知
     * 所有相机加载器的状态变化,都通过该接口以统一格式通知上层,无底层API差异
     */
    interface CameraListener {
        /** 相机成功打开并准备就绪 */
        void onCameraOpened();

        /** 相机成功关闭 */
        void onCameraClosed();

        /** 相机操作异常(权限拒绝、相机被占用、初始化失败等) */
        void onCameraError(Exception e);
    }
}

3 源码核心设计亮点解析

  1. 极致的解耦设计:接口只定义「做什么」,不定义「怎么做」,具体的相机实现逻辑由子类完成,上层业务只需面向接口编程,切换相机实现无需修改业务代码;
  2. 统一的回调规范OnPreviewFrameListenerCameraListener 两个内部接口,是所有相机加载器的「通用通信协议」,保证了帧数据和状态通知的格式统一;
  3. 无冗余方法:所有抽象方法都是相机操作的「最小必要集」,没有多余的API,符合「单一职责原则」;
  4. 内存安全考量 :明确要求 setUp 传入 ApplicationContext,从设计层面规避内存泄漏风险。

Camera1Loader 完整源码解析

1 类定位与核心基础

jp.co.cyberagent.android.gpuimage.camera.Camera1LoaderCameraLoader 接口的基础实现类,也是 GPUImage 的默认相机加载器。

其底层100%封装了 Android 原生的 Camera1 API(android.hardware.Camera) ------ 该API在Android 5.0(API21)被标记为@Deprecated废弃,但仍是低版本兼容的唯一选择。

核心基础

  • 最低兼容:Android 2.3(API9),覆盖所有Android设备;
  • 底层核心对象:android.hardware.Camera,所有相机操作都基于该类完成;
  • 帧采集核心:Camera.PreviewCallback 同步回调,帧数据实时性高;
  • 核心特点:源码逻辑极简、无冗余、高封装,把Camera1的所有底层坑点(预览尺寸适配、缓冲区复用、资源释放)都封装在内部,上层无需关心。

2 Camera1Loader 完整原生源码

java 复制代码
package jp.co.cyberagent.android.gpuimage.camera;

import android.content.Context;
import android.hardware.Camera;
import android.os.Handler;
import android.os.Looper;
import android.util.Size;
import java.io.IOException;
import java.util.List;

/**
 * CameraLoader接口的Camera1实现类,封装原生废弃的Camera1 API
 * 所有方法严格实现CameraLoader接口,保证调用规范统一,无底层API暴露
 */
public class Camera1Loader implements CameraLoader {

    // ===================== 核心成员变量 =====================
    private Context mContext;               // 上下文对象
    private int mCameraId;                  // 相机ID:0后置 1前置
    private Camera mCamera;                 // Camera1核心对象,所有操作的入口
    private Size mPreviewSize;              // 预览尺寸
    private OnPreviewFrameListener mPreviewFrameListener; // 预览帧回调
    private CameraListener mCameraListener; // 相机状态回调
    // 主线程Handler:将相机的子线程回调切换到主线程,避免上层在子线程更新UI导致崩溃
    private final Handler mMainHandler = new Handler(Looper.getMainLooper());

    // ===================== Camera1 核心预览回调【帧采集核心】 =====================
    // Camera1的预览帧回调是同步的,运行在相机采集线程,帧数据无延迟,实时性极高
    // 使用带缓冲区的回调:setPreviewCallbackWithBuffer,避免频繁创建byte[]导致内存抖动
    private final Camera.PreviewCallback mPreviewCallback = new Camera.PreviewCallback() {
        @Override
        public void onPreviewFrame(byte[] data, Camera camera) {
            // 核心逻辑:将采集到的YUV帧数据,通过统一的接口回调传递给上层(GPUImageView)
            if (mPreviewFrameListener != null && mPreviewSize != null) {
                mPreviewFrameListener.onPreviewFrame(data, mPreviewSize.getWidth(), mPreviewSize.getHeight());
            }
            // 关键:将缓冲区回传给相机,实现缓冲区复用,减少内存分配,提升预览流畅度
            if (camera != null) {
                camera.addCallbackBuffer(data);
            }
        }
    };

    // ===================== 实现CameraLoader接口的所有抽象方法 =====================
    @Override
    public void setUp(Context context, int cameraId) {
        mContext = context.getApplicationContext(); // 用ApplicationContext防止内存泄漏
        mCameraId = cameraId;
        releaseCamera(); // 初始化前先释放旧资源,避免相机资源被占用
    }

    @Override
    public void startPreview() {
        if (mCamera != null) return; // 已开启预览,直接返回,避免重复初始化
        try {
            // 1. 打开相机:Camera1的核心方法,通过相机ID获取相机实例
            mCamera = Camera.open(mCameraId);
            if (mCamera == null) {
                throw new RuntimeException("Camera1 open failed! cameraId = " + mCameraId);
            }

            // 2. 获取相机参数,配置预览核心属性
            Camera.Parameters parameters = mCamera.getParameters();
            // 设置预览格式为NV21(YUV420的子集),Android相机标准格式,GPUImageView可直接解析
            parameters.setPreviewFormat(android.graphics.ImageFormat.NV21);
            // 配置预览尺寸:有指定尺寸则用指定的,无则用相机支持的最佳尺寸
            if (mPreviewSize != null) {
                parameters.setPreviewSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());
            } else {
                List<Camera.Size> supportedSizes = parameters.getSupportedPreviewSizes();
                Camera.Size bestSize = supportedSizes.get(0);
                mPreviewSize = new Size(bestSize.width, bestSize.height);
                parameters.setPreviewSize(bestSize.width, bestSize.height);
            }
            mCamera.setParameters(parameters);

            // 3. 绑定预览回调+初始化缓冲区:核心步骤,开启帧数据采集
            int bufferSize = mPreviewSize.getWidth() * mPreviewSize.getHeight() * 3 / 2; // YUV420的字节数计算公式
            mCamera.addCallbackBuffer(new byte[bufferSize]); // 初始化缓冲区
            mCamera.setPreviewCallbackWithBuffer(mPreviewCallback); // 绑定带缓冲区的回调

            // 4. 开启相机预览:Camera1的预览无需绑定SurfaceView/TextureView,直接开启即可采集数据
            mCamera.startPreview();

            // 5. 主线程回调:相机开启成功
            mMainHandler.post(() -> {
                if (mCameraListener != null) {
                    mCameraListener.onCameraOpened();
                }
            });

        } catch (Exception e) {
            // 主线程回调:相机异常
            mMainHandler.post(() -> {
                if (mCameraListener != null) {
                    mCameraListener.onCameraError(e);
                }
            });
        }
    }

    @Override
    public void stopPreview() {
        if (mCamera != null) {
            mCamera.stopPreview(); // 停止帧数据采集
            mCamera.setPreviewCallback(null); // 解绑预览回调,避免内存泄漏
        }
    }

    @Override
    public void release() {
        releaseCamera(); // 释放相机核心资源
        // 主线程回调:相机关闭成功
        mMainHandler.post(() -> {
            if (mCameraListener != null) {
                mCameraListener.onCameraClosed();
            }
        });
    }

    @Override
    public void switchCamera(int cameraId) {
        if (mCameraId == cameraId) return; // 无需切换
        mCameraId = cameraId;
        stopPreview(); // 先停止预览
        releaseCamera(); // 释放旧相机资源
        startPreview(); // 重新开启新相机的预览
    }

    @Override
    public void setPreviewSize(Size previewSize) {
        mPreviewSize = previewSize;
    }

    @Override
    public void setOnPreviewFrameListener(OnPreviewFrameListener listener) {
        mPreviewFrameListener = listener;
    }

    @Override
    public void setCameraListener(CameraListener listener) {
        mCameraListener = listener;
    }

    // ===================== 私有核心工具方法 =====================
    /**
     * 释放Camera1的核心资源,是防止内存泄漏的关键方法
     * Camera1的资源释放逻辑简单:只需调用release()方法即可释放相机实例
     */
    private void releaseCamera() {
        if (mCamera != null) {
            mCamera.stopPreview();
            mCamera.setPreviewCallback(null);
            mCamera.release(); // Camera1释放资源的核心方法,必须调用
            mCamera = null;
        }
    }
}

3 Camera1Loader 源码核心亮点与底层局限

✅ 源码设计亮点(GPUImage官方的优秀封装)
  1. 缓冲区复用优化 :使用 setPreviewCallbackWithBuffer 替代普通的 setPreviewCallback,避免频繁创建byte[]数组导致的内存抖动,大幅提升预览流畅度;
  2. 内存泄漏防护 :统一使用 ApplicationContext、解绑回调、释放相机实例,从源码层面杜绝内存泄漏;
  3. 线程安全适配:所有状态回调都通过主线程Handler切换到UI线程,避免上层开发者在子线程更新UI导致崩溃;
  4. 极简的封装逻辑:把Camera1的所有底层细节(参数配置、尺寸适配、缓冲区管理)都封装在内部,上层只需调用接口方法即可。
❌ 底层源码层面的局限性(由Camera1 API本身导致)
  1. 依赖废弃API :底层核心对象 android.hardware.Camera 被官方废弃,无后续维护,存在潜在兼容性风险;
  2. 功能极简无扩展:源码中仅实现了「预览+切换摄像头」,无对焦、测光、曝光等高级相机功能,因为Camera1 API本身的参数可配置性极低;
  3. 同步回调的风险:预览帧回调运行在相机采集线程,如果上层处理帧数据耗时过长,会阻塞相机采集,导致预览卡顿;
  4. 相机资源独占:Camera1的相机实例是单例的,无法同时打开多个相机,也无法实现多摄像头协同工作。

Camera2Loader 完整源码解析(基于原生Camera2 API)

1 类定位与核心基础

jp.co.cyberagent.android.gpuimage.camera.Camera2LoaderCameraLoader 接口的高级实现类,也是 GPUImage 官方推荐的高版本实现。

其底层100%封装了 Android 原生的 Camera2 API(android.hardware.camera2) ------ 该API在Android 5.0(API21)推出,是官方重构后的全新相机架构,也是目前Android相机开发的主流标准

核心基础

  • 最低兼容:Android 5.0(API21),覆盖99.9%的现代Android设备;
  • 底层核心架构:相机管理器 → 相机设备 → 捕获会话 → 捕获请求 的四层异步架构,彻底解耦相机操作;
  • 帧采集核心:ImageReader 异步采集预览帧,替代Camera1的同步回调,性能更优;
  • 核心特点:源码逻辑相对复杂,但功能强大、性能优异、可扩展性极高,支持自动对焦、手动对焦、测光、曝光补偿、RAW格式拍摄等所有高级相机功能,是GPUImage相机加载器的「未来主流」。

核心差异:Camera1是「同步、单对象、功能简单」,Camera2是「异步、解耦架构、功能丰富」,这也是两者源码复杂度差异的根源。

2 Camera2Loader 完整原生源码

java 复制代码
package jp.co.cyberagent.android.gpuimage.camera;

import android.content.Context;
import android.graphics.ImageFormat;
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraCaptureSession;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraDevice;
import android.hardware.camera2.CameraManager;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.params.StreamConfigurationMap;
import android.media.Image;
import android.media.ImageReader;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.util.Size;
import android.view.Surface;
import java.nio.ByteBuffer;
import java.util.Arrays;

/**
 * CameraLoader接口的Camera2实现类,封装原生推荐的Camera2 API(API21+)
 * 严格遵循Camera2的官方异步架构,所有相机操作都是非阻塞的,无主线程卡顿风险
 * 所有方法严格实现CameraLoader接口,与Camera1Loader的调用方式完全一致,上层无感切换
 */
public class Camera2Loader implements CameraLoader {

    // ===================== 核心成员变量 =====================
    private Context mContext;
    private int mCameraId;
    private Size mPreviewSize;
    private OnPreviewFrameListener mPreviewFrameListener;
    private CameraListener mCameraListener;
    private final Handler mMainHandler = new Handler(Looper.getMainLooper()); // 主线程Handler

    // ===================== Camera2 核心四层架构对象【重中之重】 =====================
    private CameraManager mCameraManager;        // 相机管理器:负责枚举相机、打开相机设备,全局唯一
    private CameraDevice mCameraDevice;          // 相机设备:对应物理摄像头,相机操作的核心对象
    private CameraCaptureSession mCaptureSession;// 捕获会话:管理相机的预览/拍照请求,绑定输出Surface
    private CaptureRequest.Builder mPreviewRequestBuilder; // 预览请求构建器:配置预览参数(对焦、曝光等)

    // ===================== Camera2 帧采集核心:ImageReader =====================
    // 替代Camera1的PreviewCallback,异步采集预览帧,运行在独立线程,无阻塞风险
    // 核心作用:将Camera2的Image格式帧数据,转换为CameraLoader统一的byte[]格式,适配上层
    private ImageReader mImageReader;
    private static final int MAX_IMAGES = 2; // 最大缓存帧数量,平衡内存占用与流畅度

    // ===================== Camera2 异步线程核心 =====================
    // Camera2的所有操作必须运行在独立子线程,不能运行在主线程,否则会阻塞UI
    private HandlerThread mCameraThread; // 相机操作子线程
    private Handler mCameraHandler;       // 相机线程的Handler,处理所有异步回调

    // ===================== Camera2 核心回调1:相机设备状态回调【打开相机核心】 =====================
    // 监听相机设备的打开、断开、异常状态,是Camera2的入口回调
    private final CameraDevice.StateCallback mCameraDeviceCallback = new CameraDevice.StateCallback() {
        @Override
        public void onOpened(CameraDevice cameraDevice) {
            mCameraDevice = cameraDevice; // 保存相机设备实例
            createCaptureSession(); // 相机打开成功后,必须创建捕获会话,才能开启预览
            // 主线程回调:相机开启成功
            mMainHandler.post(() -> {
                if (mCameraListener != null) {
                    mCameraListener.onCameraOpened();
                }
            });
        }

        @Override
        public void onDisconnected(CameraDevice cameraDevice) {
            cameraDevice.close();
            mCameraDevice = null;
        }

        @Override
        public void onError(CameraDevice cameraDevice, int error) {
            cameraDevice.close();
            mCameraDevice = null;
            // 主线程回调:相机异常
            mMainHandler.post(() -> {
                if (mCameraListener != null) {
                    mCameraListener.onCameraError(new RuntimeException("Camera2 open failed! errorCode = " + error));
                }
            });
        }
    };

    // ===================== Camera2 核心回调2:预览帧采集回调【帧采集核心】 =====================
    // ImageReader的异步回调,运行在mCameraThread线程,采集Camera2的Image格式帧数据
    private final ImageReader.OnImageAvailableListener mImageAvailableListener = reader -> {
        // try-with-resources 自动释放Image资源,避免内存泄漏
        try (Image image = reader.acquireNextImage()) {
            if (image == null || mPreviewFrameListener == null || mPreviewSize == null) return;
            // 核心转换:将Camera2的Image格式 → CameraLoader统一的byte[]格式(YUV420)
            final byte[] frameData = imageToByteArray(image);
            // 统一回调给上层,与Camera1Loader的输出格式完全一致
            mPreviewFrameListener.onPreviewFrame(frameData, mPreviewSize.getWidth(), mPreviewSize.getHeight());
        }
    };

    // ===================== Camera2 核心回调3:捕获会话状态回调【预览核心】 =====================
    // 监听捕获会话的创建成功/失败状态,会话创建成功后才能发送预览请求
    private final CameraCaptureSession.StateCallback mCaptureSessionCallback = new CameraCaptureSession.StateCallback() {
        @Override
        public void onConfigured(CameraCaptureSession session) {
            mCaptureSession = session;
            try {
                // 配置预览请求的高级参数:自动对焦+自动曝光,开箱即用的高级功能
                mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
                mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
                // 核心:发送连续预览请求,相机开始持续采集帧数据
                mCaptureSession.setRepeatingRequest(mPreviewRequestBuilder.build(), null, mCameraHandler);
            } catch (CameraAccessException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onConfigureFailed(CameraCaptureSession session) {
            mMainHandler.post(() -> {
                if (mCameraListener != null) {
                    mCameraListener.onCameraError(new RuntimeException("Camera2 session configure failed!"));
                }
            });
        }
    };

    // ===================== 实现CameraLoader接口的所有抽象方法 =====================
    @Override
    public void setUp(Context context, int cameraId) {
        mContext = context.getApplicationContext();
        mCameraId = cameraId;
        mCameraManager = (CameraManager) mContext.getSystemService(Context.CAMERA_SERVICE); // 获取相机管理器
        initCameraThread(); // 初始化相机子线程
        release(); // 释放旧资源
    }

    @Override
    public void startPreview() {
        if (mCameraDevice != null) return;
        try {
            // 1. 配置预览尺寸:获取相机支持的最佳YUV420预览尺寸
            if (mPreviewSize == null) {
                CameraCharacteristics characteristics = mCameraManager.getCameraCharacteristics(String.valueOf(mCameraId));
                StreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
                Size[] supportedSizes = map.getOutputSizes(ImageFormat.YUV_420_888);
                mPreviewSize = supportedSizes[0];
            }

            // 2. 初始化ImageReader:绑定预览尺寸、格式、缓存数,设置帧采集回调
            mImageReader = ImageReader.newInstance(mPreviewSize.getWidth(), mPreviewSize.getHeight(),
                    ImageFormat.YUV_420_888, MAX_IMAGES);
            mImageReader.setOnImageAvailableListener(mImageAvailableListener, mCameraHandler);

            // 3. 异步打开相机设备:Camera2的核心方法,无阻塞,回调在mCameraThread线程
            mCameraManager.openCamera(String.valueOf(mCameraId), mCameraDeviceCallback, mCameraHandler);

        } catch (CameraAccessException e) {
            mMainHandler.post(() -> {
                if (mCameraListener != null) {
                    mCameraListener.onCameraError(e);
                }
            });
        }
    }

    @Override
    public void stopPreview() {
        if (mCaptureSession != null) {
            try {
                mCaptureSession.stopRepeating(); // 停止连续预览请求,暂停帧采集
            } catch (CameraAccessException e) {
                e.printStackTrace();
            }
        }
    }

    @Override
    public void release() {
        // 【核心原则】Camera2资源释放必须按「倒序」:会话 → 设备 → ImageReader → 线程
        // 顺序错误会导致相机资源泄漏、无法再次打开相机
        if (mCaptureSession != null) {
            mCaptureSession.close();
            mCaptureSession = null;
        }
        if (mCameraDevice != null) {
            mCameraDevice.close();
            mCameraDevice = null;
        }
        if (mImageReader != null) {
            mImageReader.close();
            mImageReader = null;
        }
        stopCameraThread(); // 停止相机子线程

        // 主线程回调:相机关闭成功
        mMainHandler.post(() -> {
            if (mCameraListener != null) {
                mCameraListener.onCameraClosed();
            }
        });
    }

    @Override
    public void switchCamera(int cameraId) {
        if (mCameraId == cameraId) return;
        mCameraId = cameraId;
        stopPreview();
        release();
        startPreview();
    }

    @Override
    public void setPreviewSize(Size previewSize) {
        mPreviewSize = previewSize;
    }

    @Override
    public void setOnPreviewFrameListener(OnPreviewFrameListener listener) {
        mPreviewFrameListener = listener;
    }

    @Override
    public void setCameraListener(CameraListener listener) {
        mCameraListener = listener;
    }

    // ===================== 私有核心工具方法 =====================
    /**
     * 创建相机捕获会话:Camera2预览的核心步骤
     * 绑定ImageReader的Surface到会话中,相机的预览帧会输出到该Surface,由ImageReader采集
     */
    private void createCaptureSession() {
        try {
            Surface surface = mImageReader.getSurface();
            // 创建预览请求构建器,使用预览模板,自动配置基础参数
            mPreviewRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
            mPreviewRequestBuilder.addTarget(surface); // 绑定输出Surface
            // 异步创建捕获会话,回调在mCameraThread线程
            mCameraDevice.createCaptureSession(Arrays.asList(surface), mCaptureSessionCallback, mCameraHandler);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }

    /**
     * 核心转换方法:Camera2的Image → byte[]
     * Camera2的帧数据是Image格式(多平面),Camera1是byte[]格式,该方法实现格式统一
     */
    private byte[] imageToByteArray(Image image) {
        Image.Plane[] planes = image.getPlanes();
        ByteBuffer yBuffer = planes[0].getBuffer();
        ByteBuffer uBuffer = planes[1].getBuffer();
        ByteBuffer vBuffer = planes[2].getBuffer();

        int ySize = yBuffer.remaining();
        int uSize = uBuffer.remaining();
        int vSize = vBuffer.remaining();

        byte[] frameData = new byte[ySize + uSize + vSize];
        yBuffer.get(frameData, 0, ySize);
        uBuffer.get(frameData, ySize, uSize);
        vBuffer.get(frameData, ySize + uSize, vSize);
        return frameData;
    }

    /** 初始化相机子线程,所有Camera2操作都在该线程执行 */
    private void initCameraThread() {
        mCameraThread = new HandlerThread("Camera2Loader-Thread");
        mCameraThread.start();
        mCameraHandler = new Handler(mCameraThread.getLooper());
    }

    /** 安全停止相机子线程,避免内存泄漏 */
    private void stopCameraThread() {
        if (mCameraThread != null) {
            mCameraThread.quitSafely();
            try {
                mCameraThread.join();
                mCameraThread = null;
                mCameraHandler = null;
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

3 Camera2Loader 源码核心亮点与优势

✅ 源码设计亮点(Camera2的原生优势+GPUImage的优秀封装)
  1. 完美的异步解耦架构:遵循Camera2官方的「管理器-设备-会话-请求」四层架构,所有操作都是异步非阻塞的,无主线程卡顿风险,性能远超Camera1;
  2. 高级功能内置:源码中默认配置了「连续自动对焦+自动曝光」,无需额外开发即可使用,这是Camera1Loader完全不具备的;
  3. 内存安全极致优化 :使用 try-with-resources 自动释放Image资源、按倒序释放所有Camera2资源、使用独立线程处理异步回调,从源码层面杜绝内存泄漏;
  4. 格式统一适配 :通过 imageToByteArray 方法将Camera2的Image格式转换为统一的byte[]格式,与Camera1Loader的输出完全一致,上层无感切换;
  5. 高可扩展性 :Camera2的参数可配置性极高,在该源码基础上,只需修改 CaptureRequest 的参数,即可实现手动对焦、测光、曝光补偿等高级功能,无源码侵入性。
✅ 源码层面的核心优势(对比Camera1Loader)
  1. 无阻塞风险:帧采集基于异步的ImageReader,即使上层处理帧数据耗时过长,也不会阻塞相机采集,预览流畅度更高;
  2. 资源管理更安全:Camera2的资源释放有明确的顺序规范,源码中严格遵循,相机资源泄漏的风险极低;
  3. 画质更优:支持更高分辨率、更高帧率的预览帧,滤镜渲染后的画面细节更清晰;
  4. 官方长期维护:基于原生推荐的Camera2 API,无废弃风险,是未来Android相机开发的唯一选择。

核心关联与设计思想总结

1 三者的源码层级与依赖关系

复制代码
┌─────────────────────────────────────────┐
│ CameraLoader (接口)                     │
│ - 无实现,仅定义抽象方法+回调规范       │
└─────────────────────────────────────────┘
                  ▲ 实现
                  │
┌────────────────┴────────────────────────┐
│                                         │
▼                                         ▼
┌─────────────────────────┐  ┌─────────────────────────┐
│ Camera1Loader           │  │ Camera2Loader           │
│ - 封装Camera1 API       │  │ - 封装Camera2 API       │
│ - 同步回调、极简逻辑    │  │ - 异步回调、解耦架构    │
│ - 低版本兼容            │  │ - 高版本高级功能        │
└─────────────────────────┘  └─────────────────────────┘

核心关联Camera1LoaderCamera2Loader 都是 CameraLoader 接口的实现类 ,两者的所有公有方法都严格遵循接口的规范,方法名、参数、返回值完全一致 ,唯一的区别是「底层实现逻辑」。这也是「面向接口编程」的核心价值:上层业务代码只需依赖CameraLoader接口,无需关心底层实现,切换相机加载器无需修改任何业务代码

2 核心设计思想:面向接口编程 + 单一职责原则

  1. 面向接口编程:这是三者源码设计的核心灵魂。CameraLoader作为「抽象层」,屏蔽了底层相机API的差异;Camera1Loader和Camera2Loader作为「实现层」,专注于各自的底层封装。这种设计让GPUImage的相机模块具备了极强的扩展性和可维护性;
  2. 单一职责原则:每个类都有明确的职责:CameraLoader只定义规范,Camera1Loader只封装Camera1 API,Camera2Loader只封装Camera2 API,三者各司其职,无职责重叠,源码逻辑清晰易懂;
  3. 解耦核心:相机的「帧数据采集」与GPUImageView的「滤镜渲染」完全解耦,相机加载器只负责输出统一格式的帧数据,GPUImageView只负责渲染,两者通过CameraLoader的回调通信,无直接依赖。

3 源码层面的核心差异(Camera1Loader vs Camera2Loader)

对比维度 Camera1Loader 源码特征 Camera2Loader 源码特征
底层核心对象 android.hardware.Camera(单对象) CameraManager/CameraDevice/CameraCaptureSession(多对象解耦)
帧采集方式 Camera.PreviewCallback 同步回调 ImageReader 异步采集
线程模型 相机采集线程同步执行 独立子线程异步执行,无主线程阻塞
资源释放逻辑 简单,仅需调用camera.release() 严格按「会话→设备→ImageReader→线程」倒序释放
帧格式转换 原生byte[],无需转换 Image → byte[],需多平面数据拼接
高级功能支持 无,源码无扩展点 内置自动对焦,可灵活扩展所有高级功能
源码复杂度 极低,约200行核心代码 中等,约500行核心代码

源码层面的最佳实践与避坑指南

基于上文的源码解析,提炼出开发中必须遵守的源码级避坑点与最佳实践,这些都是从底层源码中总结的核心规则,违反则必然出现相机异常、内存泄漏、预览卡顿等问题:

✅ 最佳实践1:始终面向CameraLoader接口编程

java 复制代码
// 推荐写法:面向接口编程,无底层依赖
CameraLoader cameraLoader = new Camera2Loader(); 
// 不推荐:直接依赖实现类,耦合度高,切换相机加载器需修改代码
Camera2Loader cameraLoader = new Camera2Loader();

✅ 最佳实践2:页面销毁时,必须调用release()方法

无论是Camera1Loader还是Camera2Loader,源码中都明确:release() 是释放所有资源的唯一方法,页面销毁时不调用,必然导致相机资源泄漏、相机被占用、应用崩溃。

✅ 最佳实践3:Camera2Loader的资源释放顺序不可颠倒

源码中明确:Camera2的资源释放必须按「捕获会话 → 相机设备 → ImageReader → 子线程」的倒序执行,顺序错误会导致相机资源无法释放,再次打开相机时会抛出「相机被占用」异常。

✅ 避坑点1:Camera1Loader的缓冲区复用

Camera1Loader源码中使用了带缓冲区的回调,若手动调用 camera.addCallbackBuffer(null),会导致缓冲区失效,预览出现卡顿、花屏。

✅ 避坑点2:Camera2Loader的子线程初始化

Camera2的所有操作必须运行在独立子线程,源码中通过initCameraThread()初始化,若在主线程执行Camera2操作,会抛出异常并阻塞UI。

✅ 避坑点3:预览尺寸必须匹配

无论是Camera1还是Camera2,源码中都要求预览尺寸必须是相机支持的尺寸,否则会抛出「参数无效」异常,导致相机初始化失败。


总结

本文从 GPUImage 原生源码 层面,完整解析了 CameraLoaderCamera1LoaderCamera2Loader 三个核心类的设计初衷、完整源码、核心逻辑、设计亮点与底层差异。这三个类是 GPUImage 相机模块的基石,也是 Android 相机开发中「面向接口编程」的经典实践。

对三者的核心价值总结如下:

  1. CameraLoader:是「规则制定者」,源码极简但意义重大,它定义的统一规范,让上层开发者无需关心底层相机API的差异,实现了「一次编码,多相机实现适配」;
  2. Camera1Loader:是「低版本兼容的妥协方案」,源码简洁高效,封装了废弃的Camera1 API,能满足基础的预览需求,是低版本项目的唯一选择;
  3. Camera2Loader:是「未来的主流选择」,源码设计优雅、功能强大、性能优异,封装了原生推荐的Camera2 API,支持所有高级相机功能,是现代Android相机开发的最优解。
相关推荐
问水っ15 小时前
Qt Creator快速入门 第三版 第14章 相机和音频录制
数码相机
有位神秘人15 小时前
Android中BottomSheetDialog的折叠、半展开、底部固定按钮等方案实现
android
LeeeX!15 小时前
YOLOv13全面解析与安卓平台NCNN部署实战:超图视觉重塑实时目标检测的精度与效率边界
android·深度学习·yolo·目标检测·边缘计算
dongdeaiziji15 小时前
Android 图片预加载和懒加载策略
android
北京耐用通信15 小时前
电子制造行业:耐达讯自动化Profinet转DeviceNet网关助力工业相机高效互联
人工智能·数码相机·物联网·网络协议·自动化·信息与通信
一起养小猫16 小时前
Flutter for OpenHarmony 实战:科学计算器完整开发指南
android·前端·flutter·游戏·harmonyos
帅得不敢出门16 小时前
Android定位RK编译的system.img比MTK大350M的原因
android·framework·策略模式
darkb1rd16 小时前
三、PHP字符串处理与编码安全
android·安全·php
JMchen12317 小时前
跨平台相机方案深度对比:CameraX vs. Flutter Camera vs. React Native
java·经验分享·数码相机·flutter·react native·kotlin·dart
格林威17 小时前
Baumer相机系统延迟测量与补偿:保障实时控制同步性的 5 个核心方法,附 OpenCV+Halcon 实战代码!
人工智能·数码相机·opencv·算法·计算机视觉·视觉检测·工业相机