【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相机开发的最优解。
相关推荐
格林威2 小时前
工业零件表面粗糙度评估:非接触式测量的 7 项核心技术,附 OpenCV+Halcon 实战代码!
人工智能·深度学习·数码相机·opencv·机器学习·计算机视觉·视觉检测
00后程序员张2 小时前
iOS APP 性能测试工具,监控CPU,实时日志输出
android·ios·小程序·https·uni-app·iphone·webview
invicinble2 小时前
认识es的多个维度
android·大数据·elasticsearch
博图光电2 小时前
博图双目结构光相机——叉车自动化视觉定位解决方案
运维·数码相机·自动化
前端切图仔0012 小时前
Chrome 扩展程序上架指南
android·java·javascript·google
黄林晴2 小时前
Compose Multiplatform 1.10.0 重磅发布!三大核心升级,跨平台开发效率再提升
android·android jetpack
锁我喉是吧2 小时前
Android studio 编译faiss
android·android studio·faiss
鹏程十八少2 小时前
3. Android 腾讯开源的 Shadow,凭什么成为插件化“终极方案”?
android·前端·面试
深耕AI2 小时前
【显微成像】CCD适配器(Charge-Coupled Device Adapter)
数码相机